Add support for ICE/STUN/TURN in res_rtp_asterisk and chan_sip.
[asterisk/asterisk.git] / res / pjproject / pjsip-apps / src / pjsua / pjsua_app.c
1 /* $Id$ */
2 /* 
3  * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
4  * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
19  */
20 #include <pjsua-lib/pjsua.h>
21 #include "gui.h"
22
23
24 #define THIS_FILE       "pjsua_app.c"
25 #define NO_LIMIT        (int)0x7FFFFFFF
26
27 //#define STEREO_DEMO
28 //#define TRANSPORT_ADAPTER_SAMPLE
29 //#define HAVE_MULTIPART_TEST
30
31 /* Ringtones                US         UK  */
32 #define RINGBACK_FREQ1      440     /* 400 */
33 #define RINGBACK_FREQ2      480     /* 450 */
34 #define RINGBACK_ON         2000    /* 400 */
35 #define RINGBACK_OFF        4000    /* 200 */
36 #define RINGBACK_CNT        1       /* 2   */
37 #define RINGBACK_INTERVAL   4000    /* 2000 */
38
39 #define RING_FREQ1          800
40 #define RING_FREQ2          640
41 #define RING_ON             200
42 #define RING_OFF            100
43 #define RING_CNT            3
44 #define RING_INTERVAL       3000
45
46 #define MAX_AVI             4
47
48 /* Call specific data */
49 struct call_data
50 {
51     pj_timer_entry          timer;
52     pj_bool_t               ringback_on;
53     pj_bool_t               ring_on;
54 };
55
56 /* Video settings */
57 struct app_vid
58 {
59     unsigned                vid_cnt;
60     int                     vcapture_dev;
61     int                     vrender_dev;
62     pj_bool_t               in_auto_show;
63     pj_bool_t               out_auto_transmit;
64 };
65
66 /* Pjsua application data */
67 static struct app_config
68 {
69     pjsua_config            cfg;
70     pjsua_logging_config    log_cfg;
71     pjsua_media_config      media_cfg;
72     pj_bool_t               no_refersub;
73     pj_bool_t               ipv6;
74     pj_bool_t               enable_qos;
75     pj_bool_t               no_tcp;
76     pj_bool_t               no_udp;
77     pj_bool_t               use_tls;
78     pjsua_transport_config  udp_cfg;
79     pjsua_transport_config  rtp_cfg;
80     pjsip_redirect_op       redir_op;
81
82     unsigned                acc_cnt;
83     pjsua_acc_config        acc_cfg[PJSUA_MAX_ACC];
84
85     unsigned                buddy_cnt;
86     pjsua_buddy_config      buddy_cfg[PJSUA_MAX_BUDDIES];
87
88     struct call_data        call_data[PJSUA_MAX_CALLS];
89
90     pj_pool_t              *pool;
91     /* Compatibility with older pjsua */
92
93     unsigned                codec_cnt;
94     pj_str_t                codec_arg[32];
95     unsigned                codec_dis_cnt;
96     pj_str_t                codec_dis[32];
97     pj_bool_t               null_audio;
98     unsigned                wav_count;
99     pj_str_t                wav_files[32];
100     unsigned                tone_count;
101     pjmedia_tone_desc       tones[32];
102     pjsua_conf_port_id      tone_slots[32];
103     pjsua_player_id         wav_id;
104     pjsua_conf_port_id      wav_port;
105     pj_bool_t               auto_play;
106     pj_bool_t               auto_play_hangup;
107     pj_timer_entry          auto_hangup_timer;
108     pj_bool_t               auto_loop;
109     pj_bool_t               auto_conf;
110     pj_str_t                rec_file;
111     pj_bool_t               auto_rec;
112     pjsua_recorder_id       rec_id;
113     pjsua_conf_port_id      rec_port;
114     unsigned                auto_answer;
115     unsigned                duration;
116
117 #ifdef STEREO_DEMO
118     pjmedia_snd_port       *snd;
119     pjmedia_port           *sc, *sc_ch1;
120     pjsua_conf_port_id      sc_ch1_slot;
121 #endif
122
123     float                   mic_level,
124                             speaker_level;
125
126     int                     capture_dev, playback_dev;
127     unsigned                capture_lat, playback_lat;
128
129     pj_bool_t               no_tones;
130     int                     ringback_slot;
131     int                     ringback_cnt;
132     pjmedia_port           *ringback_port;
133     int                     ring_slot;
134     int                     ring_cnt;
135     pjmedia_port           *ring_port;
136
137     struct app_vid          vid;
138     unsigned                aud_cnt;
139
140     /* AVI to play */
141     unsigned                avi_cnt;
142     struct {
143         pj_str_t                path;
144         pjmedia_vid_dev_index   dev_id;
145         pjsua_conf_port_id      slot;
146     } avi[MAX_AVI];
147     pj_bool_t               avi_auto_play;
148     int                     avi_def_idx;
149
150 } app_config;
151
152
153 //static pjsua_acc_id   current_acc;
154 #define current_acc     pjsua_acc_get_default()
155 static pjsua_call_id    current_call = PJSUA_INVALID_ID;
156 static pj_bool_t        cmd_echo;
157 static int              stdout_refresh = -1;
158 static const char      *stdout_refresh_text = "STDOUT_REFRESH";
159 static pj_bool_t        stdout_refresh_quit = PJ_FALSE;
160 static pj_str_t         uri_arg;
161
162 #if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0)
163 #   define SOME_BUF_SIZE        (1024 * 10)
164 #else
165 #   define SOME_BUF_SIZE        (1024 * 3)
166 #endif
167
168 static char some_buf[SOME_BUF_SIZE];
169
170 #ifdef STEREO_DEMO
171 static void stereo_demo();
172 #endif
173 static pj_status_t create_ipv6_media_transports(void);
174 pj_status_t app_destroy(void);
175
176 static void ringback_start(pjsua_call_id call_id);
177 static void ring_start(pjsua_call_id call_id);
178 static void ring_stop(pjsua_call_id call_id);
179
180 pj_bool_t       app_restart;
181 pj_log_func     *log_cb = NULL;
182
183 /*****************************************************************************
184  * Configuration manipulation
185  */
186
187 #if (defined(PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT) && \
188     PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT!=0) || \
189     defined(__IPHONE_4_0)
190 void keepAliveFunction(int timeout)
191 {
192     int i;
193     for (i=0; i<(int)pjsua_acc_get_count(); ++i) {
194         if (!pjsua_acc_is_valid(i))
195             continue;
196
197         if (app_config.acc_cfg[i].reg_timeout < timeout)
198             app_config.acc_cfg[i].reg_timeout = timeout;
199         pjsua_acc_set_registration(i, PJ_TRUE);
200     }
201 }
202 #endif
203
204 /* Show usage */
205 static void usage(void)
206 {
207     puts  ("Usage:");
208     puts  ("  pjsua [options] [SIP URL to call]");
209     puts  ("");
210     puts  ("General options:");
211     puts  ("  --config-file=file  Read the config/arguments from file.");
212     puts  ("  --help              Display this help screen");
213     puts  ("  --version           Display version info");
214     puts  ("");
215     puts  ("Logging options:");
216     puts  ("  --log-file=fname    Log to filename (default stderr)");
217     puts  ("  --log-level=N       Set log max level to N (0(none) to 6(trace)) (default=5)");
218     puts  ("  --app-log-level=N   Set log max level for stdout display (default=4)");
219     puts  ("  --log-append        Append instead of overwrite existing log file.\n");
220     puts  ("  --color             Use colorful logging (default yes on Win32)");
221     puts  ("  --no-color          Disable colorful logging");
222     puts  ("  --light-bg          Use dark colors for light background (default is dark bg)");
223     puts  ("  --no-stderr         Disable stderr");
224
225     puts  ("");
226     puts  ("SIP Account options:");
227     puts  ("  --use-ims           Enable 3GPP/IMS related settings on this account");
228 #if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
229     puts  ("  --use-srtp=N        Use SRTP?  0:disabled, 1:optional, 2:mandatory,");
230     puts  ("                      3:optional by duplicating media offer (def:0)");
231     puts  ("  --srtp-secure=N     SRTP require secure SIP? 0:no, 1:tls, 2:sips (def:1)");
232 #endif
233     puts  ("  --registrar=url     Set the URL of registrar server");
234     puts  ("  --id=url            Set the URL of local ID (used in From header)");
235     puts  ("  --contact=url       Optionally override the Contact information");
236     puts  ("  --contact-params=S  Append the specified parameters S in Contact header");
237     puts  ("  --contact-uri-params=S  Append the specified parameters S in Contact URI");
238     puts  ("  --proxy=url         Optional URL of proxy server to visit");
239     puts  ("                      May be specified multiple times");
240     printf("  --reg-timeout=SEC   Optional registration interval (default %d)\n",
241             PJSUA_REG_INTERVAL);
242     printf("  --rereg-delay=SEC   Optional auto retry registration interval (default %d)\n",
243             PJSUA_REG_RETRY_INTERVAL);
244     puts  ("  --reg-use-proxy=N   Control the use of proxy settings in REGISTER.");
245     puts  ("                      0=no proxy, 1=outbound only, 2=acc only, 3=all (default)");
246     puts  ("  --realm=string      Set realm");
247     puts  ("  --username=string   Set authentication username");
248     puts  ("  --password=string   Set authentication password");
249     puts  ("  --publish           Send presence PUBLISH for this account");
250     puts  ("  --mwi               Subscribe to message summary/waiting indication");
251     puts  ("  --use-100rel        Require reliable provisional response (100rel)");
252     puts  ("  --use-timer=N       Use SIP session timers? (default=1)");
253     puts  ("                      0:inactive, 1:optional, 2:mandatory, 3:always");
254     printf("  --timer-se=N        Session timers expiration period, in secs (def:%d)\n",
255             PJSIP_SESS_TIMER_DEF_SE);
256     puts  ("  --timer-min-se=N    Session timers minimum expiration period, in secs (def:90)");
257     puts  ("  --outb-rid=string   Set SIP outbound reg-id (default:1)");
258     puts  ("  --auto-update-nat=N Where N is 0 or 1 to enable/disable SIP traversal behind");
259     puts  ("                      symmetric NAT (default 1)");
260     puts  ("  --next-cred         Add another credentials");
261     puts  ("");
262     puts  ("SIP Account Control:");
263     puts  ("  --next-account      Add more account");
264     puts  ("");
265     puts  ("Transport Options:");
266 #if defined(PJ_HAS_IPV6) && PJ_HAS_IPV6
267     puts  ("  --ipv6              Use IPv6 instead for SIP and media.");
268 #endif
269     puts  ("  --set-qos           Enable QoS tagging for SIP and media.");
270     puts  ("  --local-port=port   Set TCP/UDP port. This implicitly enables both ");
271     puts  ("                      TCP and UDP transports on the specified port, unless");
272     puts  ("                      if TCP or UDP is disabled.");
273     puts  ("  --ip-addr=IP        Use the specifed address as SIP and RTP addresses.");
274     puts  ("                      (Hint: the IP may be the public IP of the NAT/router)");
275     puts  ("  --bound-addr=IP     Bind transports to this IP interface");
276     puts  ("  --no-tcp            Disable TCP transport.");
277     puts  ("  --no-udp            Disable UDP transport.");
278     puts  ("  --nameserver=NS     Add the specified nameserver to enable SRV resolution");
279     puts  ("                      This option can be specified multiple times.");
280     puts  ("  --outbound=url      Set the URL of global outbound proxy server");
281     puts  ("                      May be specified multiple times");
282     puts  ("  --stun-srv=FORMAT   Set STUN server host or domain. This option may be");
283     puts  ("                      specified more than once. FORMAT is hostdom[:PORT]");
284
285 #if defined(PJSIP_HAS_TLS_TRANSPORT) && (PJSIP_HAS_TLS_TRANSPORT != 0)
286     puts  ("");
287     puts  ("TLS Options:");
288     puts  ("  --use-tls           Enable TLS transport (default=no)");
289     puts  ("  --tls-ca-file       Specify TLS CA file (default=none)");
290     puts  ("  --tls-cert-file     Specify TLS certificate file (default=none)");
291     puts  ("  --tls-privkey-file  Specify TLS private key file (default=none)");
292     puts  ("  --tls-password      Specify TLS password to private key file (default=none)");
293     puts  ("  --tls-verify-server Verify server's certificate (default=no)");
294     puts  ("  --tls-verify-client Verify client's certificate (default=no)");
295     puts  ("  --tls-neg-timeout   Specify TLS negotiation timeout (default=no)");
296     puts  ("  --tls-srv-name      Specify TLS server name for multihosting server");
297     puts  ("  --tls-cipher        Specify prefered TLS cipher (optional).");
298     puts  ("                      May be specified multiple times");
299 #endif
300
301     puts  ("");
302     puts  ("Audio Options:");
303     puts  ("  --add-codec=name    Manually add codec (default is to enable all)");
304     puts  ("  --dis-codec=name    Disable codec (can be specified multiple times)");
305     puts  ("  --clock-rate=N      Override conference bridge clock rate");
306     puts  ("  --snd-clock-rate=N  Override sound device clock rate");
307     puts  ("  --stereo            Audio device and conference bridge opened in stereo mode");
308     puts  ("  --null-audio        Use NULL audio device");
309     puts  ("  --play-file=file    Register WAV file in conference bridge.");
310     puts  ("                      This can be specified multiple times.");
311     puts  ("  --play-tone=FORMAT  Register tone to the conference bridge.");
312     puts  ("                      FORMAT is 'F1,F2,ON,OFF', where F1,F2 are");
313     puts  ("                      frequencies, and ON,OFF=on/off duration in msec.");
314     puts  ("                      This can be specified multiple times.");
315     puts  ("  --auto-play         Automatically play the file (to incoming calls only)");
316     puts  ("  --auto-loop         Automatically loop incoming RTP to outgoing RTP");
317     puts  ("  --auto-conf         Automatically put calls in conference with others");
318     puts  ("  --rec-file=file     Open file recorder (extension can be .wav or .mp3");
319     puts  ("  --auto-rec          Automatically record conversation");
320     puts  ("  --quality=N         Specify media quality (0-10, default=6)");
321     puts  ("  --ptime=MSEC        Override codec ptime to MSEC (default=specific)");
322     puts  ("  --no-vad            Disable VAD/silence detector (default=vad enabled)");
323     puts  ("  --ec-tail=MSEC      Set echo canceller tail length (default=256)");
324     puts  ("  --ec-opt=OPT        Select echo canceller algorithm (0=default, ");
325     puts  ("                        1=speex, 2=suppressor)");
326     puts  ("  --ilbc-mode=MODE    Set iLBC codec mode (20 or 30, default is 30)");
327     puts  ("  --capture-dev=id    Audio capture device ID (default=-1)");
328     puts  ("  --playback-dev=id   Audio playback device ID (default=-1)");
329     puts  ("  --capture-lat=N     Audio capture latency, in ms (default=100)");
330     puts  ("  --playback-lat=N    Audio playback latency, in ms (default=100)");
331     puts  ("  --snd-auto-close=N  Auto close audio device when idle for N secs (default=1)");
332     puts  ("                      Specify N=-1 to disable this feature.");
333     puts  ("                      Specify N=0 for instant close when unused.");
334     puts  ("  --no-tones          Disable audible tones");
335     puts  ("  --jb-max-size       Specify jitter buffer maximum size, in frames (default=-1)");
336     puts  ("  --extra-audio       Add one more audio stream");
337
338 #if PJSUA_HAS_VIDEO
339     puts  ("");
340     puts  ("Video Options:");
341     puts  ("  --video             Enable video");
342     puts  ("  --vcapture-dev=id   Video capture device ID (default=-1)");
343     puts  ("  --vrender-dev=id    Video render device ID (default=-1)");
344     puts  ("  --play-avi=FILE     Load this AVI as virtual capture device");
345     puts  ("  --auto-play-avi     Automatically play the AVI media to call");
346 #endif
347
348     puts  ("");
349     puts  ("Media Transport Options:");
350     puts  ("  --use-ice           Enable ICE (default:no)");
351     puts  ("  --ice-regular       Use ICE regular nomination (default: aggressive)");
352     puts  ("  --ice-max-hosts=N   Set maximum number of ICE host candidates");
353     puts  ("  --ice-no-rtcp       Disable RTCP component in ICE (default: no)");
354     puts  ("  --rtp-port=N        Base port to try for RTP (default=4000)");
355     puts  ("  --rx-drop-pct=PCT   Drop PCT percent of RX RTP (for pkt lost sim, default: 0)");
356     puts  ("  --tx-drop-pct=PCT   Drop PCT percent of TX RTP (for pkt lost sim, default: 0)");
357     puts  ("  --use-turn          Enable TURN relay with ICE (default:no)");
358     puts  ("  --turn-srv          Domain or host name of TURN server (\"NAME:PORT\" format)");
359     puts  ("  --turn-tcp          Use TCP connection to TURN server (default no)");
360     puts  ("  --turn-user         TURN username");
361     puts  ("  --turn-passwd       TURN password");
362
363     puts  ("");
364     puts  ("Buddy List (can be more than one):");
365     puts  ("  --add-buddy url     Add the specified URL to the buddy list.");
366     puts  ("");
367     puts  ("User Agent options:");
368     puts  ("  --auto-answer=code  Automatically answer incoming calls with code (e.g. 200)");
369     puts  ("  --max-calls=N       Maximum number of concurrent calls (default:4, max:255)");
370     puts  ("  --thread-cnt=N      Number of worker threads (default:1)");
371     puts  ("  --duration=SEC      Set maximum call duration (default:no limit)");
372     puts  ("  --norefersub        Suppress event subscription when transfering calls");
373     puts  ("  --use-compact-form  Minimize SIP message size");
374     puts  ("  --no-force-lr       Allow strict-route to be used (i.e. do not force lr)");
375     puts  ("  --accept-redirect=N Specify how to handle call redirect (3xx) response.");
376     puts  ("                      0: reject, 1: follow automatically (default), 2: ask");
377
378     puts  ("");
379     puts  ("When URL is specified, pjsua will immediately initiate call to that URL");
380     puts  ("");
381
382     fflush(stdout);
383 }
384
385
386 /* Set default config. */
387 static void default_config(struct app_config *cfg)
388 {
389     char tmp[80];
390     unsigned i;
391
392     pjsua_config_default(&cfg->cfg);
393     pj_ansi_sprintf(tmp, "PJSUA v%s %s", pj_get_version(),
394                     pj_get_sys_info()->info.ptr);
395     pj_strdup2_with_null(app_config.pool, &cfg->cfg.user_agent, tmp);
396
397     pjsua_logging_config_default(&cfg->log_cfg);
398     pjsua_media_config_default(&cfg->media_cfg);
399     pjsua_transport_config_default(&cfg->udp_cfg);
400     cfg->udp_cfg.port = 5060;
401     pjsua_transport_config_default(&cfg->rtp_cfg);
402     cfg->rtp_cfg.port = 4000;
403     cfg->redir_op = PJSIP_REDIRECT_ACCEPT;
404     cfg->duration = NO_LIMIT;
405     cfg->wav_id = PJSUA_INVALID_ID;
406     cfg->rec_id = PJSUA_INVALID_ID;
407     cfg->wav_port = PJSUA_INVALID_ID;
408     cfg->rec_port = PJSUA_INVALID_ID;
409     cfg->mic_level = cfg->speaker_level = 1.0;
410     cfg->capture_dev = PJSUA_INVALID_ID;
411     cfg->playback_dev = PJSUA_INVALID_ID;
412     cfg->capture_lat = PJMEDIA_SND_DEFAULT_REC_LATENCY;
413     cfg->playback_lat = PJMEDIA_SND_DEFAULT_PLAY_LATENCY;
414     cfg->ringback_slot = PJSUA_INVALID_ID;
415     cfg->ring_slot = PJSUA_INVALID_ID;
416
417     for (i=0; i<PJ_ARRAY_SIZE(cfg->acc_cfg); ++i)
418         pjsua_acc_config_default(&cfg->acc_cfg[i]);
419
420     for (i=0; i<PJ_ARRAY_SIZE(cfg->buddy_cfg); ++i)
421         pjsua_buddy_config_default(&cfg->buddy_cfg[i]);
422
423     cfg->vid.vcapture_dev = PJMEDIA_VID_DEFAULT_CAPTURE_DEV;
424     cfg->vid.vrender_dev = PJMEDIA_VID_DEFAULT_RENDER_DEV;
425     cfg->aud_cnt = 1;
426
427     cfg->avi_def_idx = PJSUA_INVALID_ID;
428 }
429
430
431 /*
432  * Read command arguments from config file.
433  */
434 static int read_config_file(pj_pool_t *pool, const char *filename, 
435                             int *app_argc, char ***app_argv)
436 {
437     int i;
438     FILE *fhnd;
439     char line[200];
440     int argc = 0;
441     char **argv;
442     enum { MAX_ARGS = 128 };
443
444     /* Allocate MAX_ARGS+1 (argv needs to be terminated with NULL argument) */
445     argv = pj_pool_calloc(pool, MAX_ARGS+1, sizeof(char*));
446     argv[argc++] = *app_argv[0];
447
448     /* Open config file. */
449     fhnd = fopen(filename, "rt");
450     if (!fhnd) {
451         PJ_LOG(1,(THIS_FILE, "Unable to open config file %s", filename));
452         fflush(stdout);
453         return -1;
454     }
455
456     /* Scan tokens in the file. */
457     while (argc < MAX_ARGS && !feof(fhnd)) {
458         char  *token;
459         char  *p;
460         const char *whitespace = " \t\r\n";
461         char  cDelimiter;
462         int   len, token_len;
463         
464         if (fgets(line, sizeof(line), fhnd) == NULL) break;
465         
466         // Trim ending newlines
467         len = strlen(line);
468         if (line[len-1]=='\n')
469             line[--len] = '\0';
470         if (line[len-1]=='\r')
471             line[--len] = '\0';
472
473         if (len==0) continue;
474
475         for (p = line; *p != '\0' && argc < MAX_ARGS; p++) {
476             // first, scan whitespaces
477             while (*p != '\0' && strchr(whitespace, *p) != NULL) p++;
478
479             if (*p == '\0')                 // are we done yet?
480                 break;
481             
482             if (*p == '"' || *p == '\'') {    // is token a quoted string
483                 cDelimiter = *p++;          // save quote delimiter
484                 token = p;
485                 
486                 while (*p != '\0' && *p != cDelimiter) p++;
487                 
488                 if (*p == '\0')         // found end of the line, but,
489                     cDelimiter = '\0';  // didn't find a matching quote
490
491             } else {                    // token's not a quoted string
492                 token = p;
493                 
494                 while (*p != '\0' && strchr(whitespace, *p) == NULL) p++;
495                 
496                 cDelimiter = *p;
497             }
498             
499             *p = '\0';
500             token_len = p-token;
501             
502             if (token_len > 0) {
503                 if (*token == '#')
504                     break;  // ignore remainder of line
505                 
506                 argv[argc] = pj_pool_alloc(pool, token_len + 1);
507                 pj_memcpy(argv[argc], token, token_len + 1);
508                 ++argc;
509             }
510             
511             *p = cDelimiter;
512         }
513     }
514
515     /* Copy arguments from command line */
516     for (i=1; i<*app_argc && argc < MAX_ARGS; ++i)
517         argv[argc++] = (*app_argv)[i];
518
519     if (argc == MAX_ARGS && (i!=*app_argc || !feof(fhnd))) {
520         PJ_LOG(1,(THIS_FILE, 
521                   "Too many arguments specified in cmd line/config file"));
522         fflush(stdout);
523         fclose(fhnd);
524         return -1;
525     }
526
527     fclose(fhnd);
528
529     /* Assign the new command line back to the original command line. */
530     *app_argc = argc;
531     *app_argv = argv;
532     return 0;
533
534 }
535
536 static int my_atoi(const char *cs)
537 {
538     pj_str_t s;
539
540     pj_cstr(&s, cs);
541     if (cs[0] == '-') {
542         s.ptr++, s.slen--;
543         return 0 - (int)pj_strtoul(&s);
544     } else if (cs[0] == '+') {
545         s.ptr++, s.slen--;
546         return pj_strtoul(&s);
547     } else {
548         return pj_strtoul(&s);
549     }
550 }
551
552
553 /* Parse arguments. */
554 static pj_status_t parse_args(int argc, char *argv[],
555                               struct app_config *cfg,
556                               pj_str_t *uri_to_call)
557 {
558     int c;
559     int option_index;
560     enum { OPT_CONFIG_FILE=127, OPT_LOG_FILE, OPT_LOG_LEVEL, OPT_APP_LOG_LEVEL, 
561            OPT_LOG_APPEND, OPT_COLOR, OPT_NO_COLOR, OPT_LIGHT_BG, OPT_NO_STDERR,
562            OPT_HELP, OPT_VERSION, OPT_NULL_AUDIO, OPT_SND_AUTO_CLOSE,
563            OPT_LOCAL_PORT, OPT_IP_ADDR, OPT_PROXY, OPT_OUTBOUND_PROXY, 
564            OPT_REGISTRAR, OPT_REG_TIMEOUT, OPT_PUBLISH, OPT_ID, OPT_CONTACT,
565            OPT_BOUND_ADDR, OPT_CONTACT_PARAMS, OPT_CONTACT_URI_PARAMS,
566            OPT_100REL, OPT_USE_IMS, OPT_REALM, OPT_USERNAME, OPT_PASSWORD,
567            OPT_REG_RETRY_INTERVAL, OPT_REG_USE_PROXY,
568            OPT_MWI, OPT_NAMESERVER, OPT_STUN_SRV, OPT_OUTB_RID,
569            OPT_ADD_BUDDY, OPT_OFFER_X_MS_MSG, OPT_NO_PRESENCE,
570            OPT_AUTO_ANSWER, OPT_AUTO_PLAY, OPT_AUTO_PLAY_HANGUP, OPT_AUTO_LOOP,
571            OPT_AUTO_CONF, OPT_CLOCK_RATE, OPT_SND_CLOCK_RATE, OPT_STEREO,
572            OPT_USE_ICE, OPT_ICE_REGULAR, OPT_USE_SRTP, OPT_SRTP_SECURE,
573            OPT_USE_TURN, OPT_ICE_MAX_HOSTS, OPT_ICE_NO_RTCP, OPT_TURN_SRV, 
574            OPT_TURN_TCP, OPT_TURN_USER, OPT_TURN_PASSWD,
575            OPT_PLAY_FILE, OPT_PLAY_TONE, OPT_RTP_PORT, OPT_ADD_CODEC, 
576            OPT_ILBC_MODE, OPT_REC_FILE, OPT_AUTO_REC,
577            OPT_COMPLEXITY, OPT_QUALITY, OPT_PTIME, OPT_NO_VAD,
578            OPT_RX_DROP_PCT, OPT_TX_DROP_PCT, OPT_EC_TAIL, OPT_EC_OPT,
579            OPT_NEXT_ACCOUNT, OPT_NEXT_CRED, OPT_MAX_CALLS, 
580            OPT_DURATION, OPT_NO_TCP, OPT_NO_UDP, OPT_THREAD_CNT,
581            OPT_NOREFERSUB, OPT_ACCEPT_REDIRECT,
582            OPT_USE_TLS, OPT_TLS_CA_FILE, OPT_TLS_CERT_FILE, OPT_TLS_PRIV_FILE,
583            OPT_TLS_PASSWORD, OPT_TLS_VERIFY_SERVER, OPT_TLS_VERIFY_CLIENT,
584            OPT_TLS_NEG_TIMEOUT, OPT_TLS_CIPHER,
585            OPT_CAPTURE_DEV, OPT_PLAYBACK_DEV,
586            OPT_CAPTURE_LAT, OPT_PLAYBACK_LAT, OPT_NO_TONES, OPT_JB_MAX_SIZE,
587            OPT_STDOUT_REFRESH, OPT_STDOUT_REFRESH_TEXT, OPT_IPV6, OPT_QOS,
588 #ifdef _IONBF
589            OPT_STDOUT_NO_BUF,
590 #endif
591            OPT_AUTO_UPDATE_NAT,OPT_USE_COMPACT_FORM,OPT_DIS_CODEC,
592            OPT_NO_FORCE_LR,
593            OPT_TIMER, OPT_TIMER_SE, OPT_TIMER_MIN_SE,
594            OPT_VIDEO, OPT_EXTRA_AUDIO,
595            OPT_VCAPTURE_DEV, OPT_VRENDER_DEV, OPT_PLAY_AVI, OPT_AUTO_PLAY_AVI
596     };
597     struct pj_getopt_option long_options[] = {
598         { "config-file",1, 0, OPT_CONFIG_FILE},
599         { "log-file",   1, 0, OPT_LOG_FILE},
600         { "log-level",  1, 0, OPT_LOG_LEVEL},
601         { "app-log-level",1,0,OPT_APP_LOG_LEVEL},
602         { "log-append", 0, 0, OPT_LOG_APPEND},
603         { "color",      0, 0, OPT_COLOR},
604         { "no-color",   0, 0, OPT_NO_COLOR},
605         { "light-bg",           0, 0, OPT_LIGHT_BG},
606         { "no-stderr",  0, 0, OPT_NO_STDERR},
607         { "help",       0, 0, OPT_HELP},
608         { "version",    0, 0, OPT_VERSION},
609         { "clock-rate", 1, 0, OPT_CLOCK_RATE},
610         { "snd-clock-rate",     1, 0, OPT_SND_CLOCK_RATE},
611         { "stereo",     0, 0, OPT_STEREO},
612         { "null-audio", 0, 0, OPT_NULL_AUDIO},
613         { "local-port", 1, 0, OPT_LOCAL_PORT},
614         { "ip-addr",    1, 0, OPT_IP_ADDR},
615         { "bound-addr", 1, 0, OPT_BOUND_ADDR},
616         { "no-tcp",     0, 0, OPT_NO_TCP},
617         { "no-udp",     0, 0, OPT_NO_UDP},
618         { "norefersub", 0, 0, OPT_NOREFERSUB},
619         { "proxy",      1, 0, OPT_PROXY},
620         { "outbound",   1, 0, OPT_OUTBOUND_PROXY},
621         { "registrar",  1, 0, OPT_REGISTRAR},
622         { "reg-timeout",1, 0, OPT_REG_TIMEOUT},
623         { "publish",    0, 0, OPT_PUBLISH},
624         { "mwi",        0, 0, OPT_MWI},
625         { "use-100rel", 0, 0, OPT_100REL},
626         { "use-ims",    0, 0, OPT_USE_IMS},
627         { "id",         1, 0, OPT_ID},
628         { "contact",    1, 0, OPT_CONTACT},
629         { "contact-params",1,0, OPT_CONTACT_PARAMS},
630         { "contact-uri-params",1,0, OPT_CONTACT_URI_PARAMS},
631         { "auto-update-nat",    1, 0, OPT_AUTO_UPDATE_NAT},
632         { "use-compact-form",   0, 0, OPT_USE_COMPACT_FORM},
633         { "accept-redirect", 1, 0, OPT_ACCEPT_REDIRECT},
634         { "no-force-lr",0, 0, OPT_NO_FORCE_LR},
635         { "realm",      1, 0, OPT_REALM},
636         { "username",   1, 0, OPT_USERNAME},
637         { "password",   1, 0, OPT_PASSWORD},
638         { "rereg-delay",1, 0, OPT_REG_RETRY_INTERVAL},
639         { "reg-use-proxy", 1, 0, OPT_REG_USE_PROXY},
640         { "nameserver", 1, 0, OPT_NAMESERVER},
641         { "stun-srv",   1, 0, OPT_STUN_SRV},
642         { "add-buddy",  1, 0, OPT_ADD_BUDDY},
643         { "offer-x-ms-msg",0,0,OPT_OFFER_X_MS_MSG},
644         { "no-presence", 0, 0, OPT_NO_PRESENCE},
645         { "auto-answer",1, 0, OPT_AUTO_ANSWER},
646         { "auto-play",  0, 0, OPT_AUTO_PLAY},
647         { "auto-play-hangup",0, 0, OPT_AUTO_PLAY_HANGUP},
648         { "auto-rec",   0, 0, OPT_AUTO_REC},
649         { "auto-loop",  0, 0, OPT_AUTO_LOOP},
650         { "auto-conf",  0, 0, OPT_AUTO_CONF},
651         { "play-file",  1, 0, OPT_PLAY_FILE},
652         { "play-tone",  1, 0, OPT_PLAY_TONE},
653         { "rec-file",   1, 0, OPT_REC_FILE},
654         { "rtp-port",   1, 0, OPT_RTP_PORT},
655
656         { "use-ice",    0, 0, OPT_USE_ICE},
657         { "ice-regular",0, 0, OPT_ICE_REGULAR},
658         { "use-turn",   0, 0, OPT_USE_TURN},
659         { "ice-max-hosts",1, 0, OPT_ICE_MAX_HOSTS},
660         { "ice-no-rtcp",0, 0, OPT_ICE_NO_RTCP},
661         { "turn-srv",   1, 0, OPT_TURN_SRV},
662         { "turn-tcp",   0, 0, OPT_TURN_TCP},
663         { "turn-user",  1, 0, OPT_TURN_USER},
664         { "turn-passwd",1, 0, OPT_TURN_PASSWD},
665
666 #if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
667         { "use-srtp",   1, 0, OPT_USE_SRTP},
668         { "srtp-secure",1, 0, OPT_SRTP_SECURE},
669 #endif
670         { "add-codec",  1, 0, OPT_ADD_CODEC},
671         { "dis-codec",  1, 0, OPT_DIS_CODEC},
672         { "complexity", 1, 0, OPT_COMPLEXITY},
673         { "quality",    1, 0, OPT_QUALITY},
674         { "ptime",      1, 0, OPT_PTIME},
675         { "no-vad",     0, 0, OPT_NO_VAD},
676         { "ec-tail",    1, 0, OPT_EC_TAIL},
677         { "ec-opt",     1, 0, OPT_EC_OPT},
678         { "ilbc-mode",  1, 0, OPT_ILBC_MODE},
679         { "rx-drop-pct",1, 0, OPT_RX_DROP_PCT},
680         { "tx-drop-pct",1, 0, OPT_TX_DROP_PCT},
681         { "next-account",0,0, OPT_NEXT_ACCOUNT},
682         { "next-cred",  0, 0, OPT_NEXT_CRED},
683         { "max-calls",  1, 0, OPT_MAX_CALLS},
684         { "duration",   1, 0, OPT_DURATION},
685         { "thread-cnt", 1, 0, OPT_THREAD_CNT},
686 #if defined(PJSIP_HAS_TLS_TRANSPORT) && (PJSIP_HAS_TLS_TRANSPORT != 0)
687         { "use-tls",    0, 0, OPT_USE_TLS}, 
688         { "tls-ca-file",1, 0, OPT_TLS_CA_FILE},
689         { "tls-cert-file",1,0, OPT_TLS_CERT_FILE}, 
690         { "tls-privkey-file",1,0, OPT_TLS_PRIV_FILE},
691         { "tls-password",1,0, OPT_TLS_PASSWORD},
692         { "tls-verify-server", 0, 0, OPT_TLS_VERIFY_SERVER},
693         { "tls-verify-client", 0, 0, OPT_TLS_VERIFY_CLIENT},
694         { "tls-neg-timeout", 1, 0, OPT_TLS_NEG_TIMEOUT},
695         { "tls-cipher", 1, 0, OPT_TLS_CIPHER},
696 #endif
697         { "capture-dev",    1, 0, OPT_CAPTURE_DEV},
698         { "playback-dev",   1, 0, OPT_PLAYBACK_DEV},
699         { "capture-lat",    1, 0, OPT_CAPTURE_LAT},
700         { "playback-lat",   1, 0, OPT_PLAYBACK_LAT},
701         { "stdout-refresh", 1, 0, OPT_STDOUT_REFRESH},
702         { "stdout-refresh-text", 1, 0, OPT_STDOUT_REFRESH_TEXT},
703 #ifdef _IONBF
704         { "stdout-no-buf",  0, 0, OPT_STDOUT_NO_BUF },
705 #endif
706         { "snd-auto-close", 1, 0, OPT_SND_AUTO_CLOSE},
707         { "no-tones",    0, 0, OPT_NO_TONES},
708         { "jb-max-size", 1, 0, OPT_JB_MAX_SIZE},
709 #if defined(PJ_HAS_IPV6) && PJ_HAS_IPV6
710         { "ipv6",        0, 0, OPT_IPV6},
711 #endif
712         { "set-qos",     0, 0, OPT_QOS},
713         { "use-timer",  1, 0, OPT_TIMER},
714         { "timer-se",   1, 0, OPT_TIMER_SE},
715         { "timer-min-se", 1, 0, OPT_TIMER_MIN_SE},
716         { "outb-rid",   1, 0, OPT_OUTB_RID},
717         { "video",      0, 0, OPT_VIDEO},
718         { "extra-audio",0, 0, OPT_EXTRA_AUDIO},
719         { "vcapture-dev", 1, 0, OPT_VCAPTURE_DEV},
720         { "vrender-dev",  1, 0, OPT_VRENDER_DEV},
721         { "play-avi",   1, 0, OPT_PLAY_AVI},
722         { "auto-play-avi", 0, 0, OPT_AUTO_PLAY_AVI},
723         { NULL, 0, 0, 0}
724     };
725     pj_status_t status;
726     pjsua_acc_config *cur_acc;
727     char *config_file = NULL;
728     unsigned i;
729
730     /* Run pj_getopt once to see if user specifies config file to read. */ 
731     pj_optind = 0;
732     while ((c=pj_getopt_long(argc, argv, "", long_options, 
733                              &option_index)) != -1) 
734     {
735         switch (c) {
736         case OPT_CONFIG_FILE:
737             config_file = pj_optarg;
738             break;
739         }
740         if (config_file)
741             break;
742     }
743
744     if (config_file) {
745         status = read_config_file(app_config.pool, config_file, &argc, &argv);
746         if (status != 0)
747             return status;
748     }
749
750     cfg->acc_cnt = 0;
751     cur_acc = &cfg->acc_cfg[0];
752
753
754     /* Reinitialize and re-run pj_getopt again, possibly with new arguments
755      * read from config file.
756      */
757     pj_optind = 0;
758     while((c=pj_getopt_long(argc,argv, "", long_options,&option_index))!=-1) {
759         pj_str_t tmp;
760         long lval;
761
762         switch (c) {
763
764         case OPT_CONFIG_FILE:
765             /* Ignore as this has been processed before */
766             break;
767         
768         case OPT_LOG_FILE:
769             cfg->log_cfg.log_filename = pj_str(pj_optarg);
770             break;
771
772         case OPT_LOG_LEVEL:
773             c = pj_strtoul(pj_cstr(&tmp, pj_optarg));
774             if (c < 0 || c > 6) {
775                 PJ_LOG(1,(THIS_FILE, 
776                           "Error: expecting integer value 0-6 "
777                           "for --log-level"));
778                 return PJ_EINVAL;
779             }
780             cfg->log_cfg.level = c;
781             pj_log_set_level( c );
782             break;
783
784         case OPT_APP_LOG_LEVEL:
785             cfg->log_cfg.console_level = pj_strtoul(pj_cstr(&tmp, pj_optarg));
786             if (cfg->log_cfg.console_level < 0 || cfg->log_cfg.console_level > 6) {
787                 PJ_LOG(1,(THIS_FILE, 
788                           "Error: expecting integer value 0-6 "
789                           "for --app-log-level"));
790                 return PJ_EINVAL;
791             }
792             break;
793
794         case OPT_LOG_APPEND:
795             cfg->log_cfg.log_file_flags |= PJ_O_APPEND;
796             break;
797
798         case OPT_COLOR:
799             cfg->log_cfg.decor |= PJ_LOG_HAS_COLOR;
800             break;
801
802         case OPT_NO_COLOR:
803             cfg->log_cfg.decor &= ~PJ_LOG_HAS_COLOR;
804             break;
805
806         case OPT_LIGHT_BG:
807             pj_log_set_color(1, PJ_TERM_COLOR_R);
808             pj_log_set_color(2, PJ_TERM_COLOR_R | PJ_TERM_COLOR_G);
809             pj_log_set_color(3, PJ_TERM_COLOR_B | PJ_TERM_COLOR_G);
810             pj_log_set_color(4, 0);
811             pj_log_set_color(5, 0);
812             pj_log_set_color(77, 0);
813             break;
814
815         case OPT_NO_STDERR:
816             freopen("/dev/null", "w", stderr);
817             break;
818
819         case OPT_HELP:
820             usage();
821             return PJ_EINVAL;
822
823         case OPT_VERSION:   /* version */
824             pj_dump_config();
825             return PJ_EINVAL;
826
827         case OPT_NULL_AUDIO:
828             cfg->null_audio = PJ_TRUE;
829             break;
830
831         case OPT_CLOCK_RATE:
832             lval = pj_strtoul(pj_cstr(&tmp, pj_optarg));
833             if (lval < 8000 || lval > 192000) {
834                 PJ_LOG(1,(THIS_FILE, "Error: expecting value between "
835                                      "8000-192000 for conference clock rate"));
836                 return PJ_EINVAL;
837             }
838             cfg->media_cfg.clock_rate = lval; 
839             break;
840
841         case OPT_SND_CLOCK_RATE:
842             lval = pj_strtoul(pj_cstr(&tmp, pj_optarg));
843             if (lval < 8000 || lval > 192000) {
844                 PJ_LOG(1,(THIS_FILE, "Error: expecting value between "
845                                      "8000-192000 for sound device clock rate"));
846                 return PJ_EINVAL;
847             }
848             cfg->media_cfg.snd_clock_rate = lval; 
849             break;
850
851         case OPT_STEREO:
852             cfg->media_cfg.channel_count = 2;
853             break;
854
855         case OPT_LOCAL_PORT:   /* local-port */
856             lval = pj_strtoul(pj_cstr(&tmp, pj_optarg));
857             if (lval < 0 || lval > 65535) {
858                 PJ_LOG(1,(THIS_FILE, 
859                           "Error: expecting integer value for "
860                           "--local-port"));
861                 return PJ_EINVAL;
862             }
863             cfg->udp_cfg.port = (pj_uint16_t)lval;
864             break;
865
866         case OPT_IP_ADDR: /* ip-addr */
867             cfg->udp_cfg.public_addr = pj_str(pj_optarg);
868             cfg->rtp_cfg.public_addr = pj_str(pj_optarg);
869             break;
870
871         case OPT_BOUND_ADDR: /* bound-addr */
872             cfg->udp_cfg.bound_addr = pj_str(pj_optarg);
873             cfg->rtp_cfg.bound_addr = pj_str(pj_optarg);
874             break;
875
876         case OPT_NO_UDP: /* no-udp */
877             if (cfg->no_tcp) {
878               PJ_LOG(1,(THIS_FILE,"Error: can not disable both TCP and UDP"));
879               return PJ_EINVAL;
880             }
881
882             cfg->no_udp = PJ_TRUE;
883             break;
884
885         case OPT_NOREFERSUB: /* norefersub */
886             cfg->no_refersub = PJ_TRUE;
887             break;
888
889         case OPT_NO_TCP: /* no-tcp */
890             if (cfg->no_udp) {
891               PJ_LOG(1,(THIS_FILE,"Error: can not disable both TCP and UDP"));
892               return PJ_EINVAL;
893             }
894
895             cfg->no_tcp = PJ_TRUE;
896             break;
897
898         case OPT_PROXY:   /* proxy */
899             if (pjsua_verify_sip_url(pj_optarg) != 0) {
900                 PJ_LOG(1,(THIS_FILE, 
901                           "Error: invalid SIP URL '%s' "
902                           "in proxy argument", pj_optarg));
903                 return PJ_EINVAL;
904             }
905             cur_acc->proxy[cur_acc->proxy_cnt++] = pj_str(pj_optarg);
906             break;
907
908         case OPT_OUTBOUND_PROXY:   /* outbound proxy */
909             if (pjsua_verify_sip_url(pj_optarg) != 0) {
910                 PJ_LOG(1,(THIS_FILE, 
911                           "Error: invalid SIP URL '%s' "
912                           "in outbound proxy argument", pj_optarg));
913                 return PJ_EINVAL;
914             }
915             cfg->cfg.outbound_proxy[cfg->cfg.outbound_proxy_cnt++] = pj_str(pj_optarg);
916             break;
917
918         case OPT_REGISTRAR:   /* registrar */
919             if (pjsua_verify_sip_url(pj_optarg) != 0) {
920                 PJ_LOG(1,(THIS_FILE, 
921                           "Error: invalid SIP URL '%s' in "
922                           "registrar argument", pj_optarg));
923                 return PJ_EINVAL;
924             }
925             cur_acc->reg_uri = pj_str(pj_optarg);
926             break;
927
928         case OPT_REG_TIMEOUT:   /* reg-timeout */
929             cur_acc->reg_timeout = pj_strtoul(pj_cstr(&tmp,pj_optarg));
930             if (cur_acc->reg_timeout < 1 || cur_acc->reg_timeout > 3600) {
931                 PJ_LOG(1,(THIS_FILE, 
932                           "Error: invalid value for --reg-timeout "
933                           "(expecting 1-3600)"));
934                 return PJ_EINVAL;
935             }
936             break;
937
938         case OPT_PUBLISH:   /* publish */
939             cur_acc->publish_enabled = PJ_TRUE;
940             break;
941
942         case OPT_MWI:   /* mwi */
943             cur_acc->mwi_enabled = PJ_TRUE;
944             break;
945
946         case OPT_100REL: /** 100rel */
947             cur_acc->require_100rel = PJSUA_100REL_MANDATORY;
948             cfg->cfg.require_100rel = PJSUA_100REL_MANDATORY;
949             break;
950
951         case OPT_TIMER: /** session timer */
952             lval = pj_strtoul(pj_cstr(&tmp, pj_optarg));
953             if (lval < 0 || lval > 3) {
954                 PJ_LOG(1,(THIS_FILE, 
955                           "Error: expecting integer value 0-3 for --use-timer"));
956                 return PJ_EINVAL;
957             }
958             cur_acc->use_timer = lval;
959             cfg->cfg.use_timer = lval;
960             break;
961
962         case OPT_TIMER_SE: /** session timer session expiration */
963             cur_acc->timer_setting.sess_expires = pj_strtoul(pj_cstr(&tmp, pj_optarg));
964             if (cur_acc->timer_setting.sess_expires < 90) {
965                 PJ_LOG(1,(THIS_FILE, 
966                           "Error: invalid value for --timer-se "
967                           "(expecting higher than 90)"));
968                 return PJ_EINVAL;
969             }
970             cfg->cfg.timer_setting.sess_expires = cur_acc->timer_setting.sess_expires;
971             break;
972
973         case OPT_TIMER_MIN_SE: /** session timer minimum session expiration */
974             cur_acc->timer_setting.min_se = pj_strtoul(pj_cstr(&tmp, pj_optarg));
975             if (cur_acc->timer_setting.min_se < 90) {
976                 PJ_LOG(1,(THIS_FILE, 
977                           "Error: invalid value for --timer-min-se "
978                           "(expecting higher than 90)"));
979                 return PJ_EINVAL;
980             }
981             cfg->cfg.timer_setting.min_se = cur_acc->timer_setting.min_se;
982             break;
983
984         case OPT_OUTB_RID: /* Outbound reg-id */
985             cur_acc->rfc5626_reg_id = pj_str(pj_optarg);
986             break;
987
988         case OPT_USE_IMS: /* Activate IMS settings */
989             cur_acc->auth_pref.initial_auth = PJ_TRUE;
990             break;
991
992         case OPT_ID:   /* id */
993             if (pjsua_verify_url(pj_optarg) != 0) {
994                 PJ_LOG(1,(THIS_FILE, 
995                           "Error: invalid SIP URL '%s' "
996                           "in local id argument", pj_optarg));
997                 return PJ_EINVAL;
998             }
999             cur_acc->id = pj_str(pj_optarg);
1000             break;
1001
1002         case OPT_CONTACT:   /* contact */
1003             if (pjsua_verify_sip_url(pj_optarg) != 0) {
1004                 PJ_LOG(1,(THIS_FILE, 
1005                           "Error: invalid SIP URL '%s' "
1006                           "in contact argument", pj_optarg));
1007                 return PJ_EINVAL;
1008             }
1009             cur_acc->force_contact = pj_str(pj_optarg);
1010             break;
1011
1012         case OPT_CONTACT_PARAMS:
1013             cur_acc->contact_params = pj_str(pj_optarg);
1014             break;
1015
1016         case OPT_CONTACT_URI_PARAMS:
1017             cur_acc->contact_uri_params = pj_str(pj_optarg);
1018             break;
1019
1020         case OPT_AUTO_UPDATE_NAT:   /* OPT_AUTO_UPDATE_NAT */
1021             cur_acc->allow_contact_rewrite  = pj_strtoul(pj_cstr(&tmp, pj_optarg));
1022             break;
1023
1024         case OPT_USE_COMPACT_FORM:
1025             /* enable compact form - from Ticket #342 */
1026             {
1027                 extern pj_bool_t pjsip_use_compact_form;
1028                 extern pj_bool_t pjsip_include_allow_hdr_in_dlg;
1029                 extern pj_bool_t pjmedia_add_rtpmap_for_static_pt;
1030
1031                 pjsip_use_compact_form = PJ_TRUE;
1032                 /* do not transmit Allow header */
1033                 pjsip_include_allow_hdr_in_dlg = PJ_FALSE;
1034                 /* Do not include rtpmap for static payload types (<96) */
1035                 pjmedia_add_rtpmap_for_static_pt = PJ_FALSE;
1036             }
1037             break;
1038
1039         case OPT_ACCEPT_REDIRECT:
1040             cfg->redir_op = my_atoi(pj_optarg);
1041             if (cfg->redir_op<0 || cfg->redir_op>PJSIP_REDIRECT_STOP) {
1042                 PJ_LOG(1,(THIS_FILE, 
1043                           "Error: accept-redirect value '%s' ", pj_optarg));
1044                 return PJ_EINVAL;
1045             }
1046             break;
1047
1048         case OPT_NO_FORCE_LR:
1049             cfg->cfg.force_lr = PJ_FALSE;
1050             break;
1051
1052         case OPT_NEXT_ACCOUNT: /* Add more account. */
1053             cfg->acc_cnt++;
1054             cur_acc = &cfg->acc_cfg[cfg->acc_cnt];
1055             break;
1056
1057         case OPT_USERNAME:   /* Default authentication user */
1058             cur_acc->cred_info[cur_acc->cred_count].username = pj_str(pj_optarg);
1059             cur_acc->cred_info[cur_acc->cred_count].scheme = pj_str("Digest");
1060             break;
1061
1062         case OPT_REALM:     /* Default authentication realm. */
1063             cur_acc->cred_info[cur_acc->cred_count].realm = pj_str(pj_optarg);
1064             break;
1065
1066         case OPT_PASSWORD:   /* authentication password */
1067             cur_acc->cred_info[cur_acc->cred_count].data_type = PJSIP_CRED_DATA_PLAIN_PASSWD;
1068             cur_acc->cred_info[cur_acc->cred_count].data = pj_str(pj_optarg);
1069 #if PJSIP_HAS_DIGEST_AKA_AUTH
1070             cur_acc->cred_info[cur_acc->cred_count].data_type |= PJSIP_CRED_DATA_EXT_AKA;
1071             cur_acc->cred_info[cur_acc->cred_count].ext.aka.k = pj_str(pj_optarg);
1072             cur_acc->cred_info[cur_acc->cred_count].ext.aka.cb = &pjsip_auth_create_aka_response;
1073 #endif
1074             break;
1075
1076         case OPT_REG_RETRY_INTERVAL:
1077             cur_acc->reg_retry_interval = pj_strtoul(pj_cstr(&tmp, pj_optarg));
1078             break;
1079
1080         case OPT_REG_USE_PROXY:
1081             cur_acc->reg_use_proxy = (unsigned)pj_strtoul(pj_cstr(&tmp, pj_optarg));
1082             if (cur_acc->reg_use_proxy > 3) {
1083                 PJ_LOG(1,(THIS_FILE, "Error: invalid --reg-use-proxy value '%s'",
1084                           pj_optarg));
1085                 return PJ_EINVAL;
1086             }
1087             break;
1088
1089         case OPT_NEXT_CRED: /* next credential */
1090             cur_acc->cred_count++;
1091             break;
1092
1093         case OPT_NAMESERVER: /* nameserver */
1094             cfg->cfg.nameserver[cfg->cfg.nameserver_count++] = pj_str(pj_optarg);
1095             if (cfg->cfg.nameserver_count > PJ_ARRAY_SIZE(cfg->cfg.nameserver)) {
1096                 PJ_LOG(1,(THIS_FILE, "Error: too many nameservers"));
1097                 return PJ_ETOOMANY;
1098             }
1099             break;
1100
1101         case OPT_STUN_SRV:   /* STUN server */
1102             cfg->cfg.stun_host = pj_str(pj_optarg);
1103             if (cfg->cfg.stun_srv_cnt==PJ_ARRAY_SIZE(cfg->cfg.stun_srv)) {
1104                 PJ_LOG(1,(THIS_FILE, "Error: too many STUN servers"));
1105                 return PJ_ETOOMANY;
1106             }
1107             cfg->cfg.stun_srv[cfg->cfg.stun_srv_cnt++] = pj_str(pj_optarg);
1108             break;
1109
1110         case OPT_ADD_BUDDY: /* Add to buddy list. */
1111             if (pjsua_verify_url(pj_optarg) != 0) {
1112                 PJ_LOG(1,(THIS_FILE, 
1113                           "Error: invalid URL '%s' in "
1114                           "--add-buddy option", pj_optarg));
1115                 return -1;
1116             }
1117             if (cfg->buddy_cnt == PJ_ARRAY_SIZE(cfg->buddy_cfg)) {
1118                 PJ_LOG(1,(THIS_FILE, 
1119                           "Error: too many buddies in buddy list."));
1120                 return -1;
1121             }
1122             cfg->buddy_cfg[cfg->buddy_cnt].uri = pj_str(pj_optarg);
1123             cfg->buddy_cnt++;
1124             break;
1125
1126         case OPT_AUTO_PLAY:
1127             cfg->auto_play = 1;
1128             break;
1129
1130         case OPT_AUTO_PLAY_HANGUP:
1131             cfg->auto_play_hangup = 1;
1132             break;
1133
1134         case OPT_AUTO_REC:
1135             cfg->auto_rec = 1;
1136             break;
1137
1138         case OPT_AUTO_LOOP:
1139             cfg->auto_loop = 1;
1140             break;
1141
1142         case OPT_AUTO_CONF:
1143             cfg->auto_conf = 1;
1144             break;
1145
1146         case OPT_PLAY_FILE:
1147             cfg->wav_files[cfg->wav_count++] = pj_str(pj_optarg);
1148             break;
1149
1150         case OPT_PLAY_TONE:
1151             {
1152                 int f1, f2, on, off;
1153                 int n;
1154
1155                 n = sscanf(pj_optarg, "%d,%d,%d,%d", &f1, &f2, &on, &off);
1156                 if (n != 4) {
1157                     puts("Expecting f1,f2,on,off in --play-tone");
1158                     return -1;
1159                 }
1160
1161                 cfg->tones[cfg->tone_count].freq1 = (short)f1;
1162                 cfg->tones[cfg->tone_count].freq2 = (short)f2;
1163                 cfg->tones[cfg->tone_count].on_msec = (short)on;
1164                 cfg->tones[cfg->tone_count].off_msec = (short)off;
1165                 ++cfg->tone_count;
1166             }
1167             break;
1168
1169         case OPT_REC_FILE:
1170             cfg->rec_file = pj_str(pj_optarg);
1171             break;
1172
1173         case OPT_USE_ICE:
1174             cfg->media_cfg.enable_ice = PJ_TRUE;
1175             break;
1176
1177         case OPT_ICE_REGULAR:
1178             cfg->media_cfg.ice_opt.aggressive = PJ_FALSE;
1179             break;
1180
1181         case OPT_USE_TURN:
1182             cfg->media_cfg.enable_turn = PJ_TRUE;
1183             break;
1184
1185         case OPT_ICE_MAX_HOSTS:
1186             cfg->media_cfg.ice_max_host_cands = my_atoi(pj_optarg);
1187             break;
1188
1189         case OPT_ICE_NO_RTCP:
1190             cfg->media_cfg.ice_no_rtcp = PJ_TRUE;
1191             break;
1192
1193         case OPT_TURN_SRV:
1194             cfg->media_cfg.turn_server = pj_str(pj_optarg);
1195             break;
1196
1197         case OPT_TURN_TCP:
1198             cfg->media_cfg.turn_conn_type = PJ_TURN_TP_TCP;
1199             break;
1200
1201         case OPT_TURN_USER:
1202             cfg->media_cfg.turn_auth_cred.type = PJ_STUN_AUTH_CRED_STATIC;
1203             cfg->media_cfg.turn_auth_cred.data.static_cred.realm = pj_str("*");
1204             cfg->media_cfg.turn_auth_cred.data.static_cred.username = pj_str(pj_optarg);
1205             break;
1206
1207         case OPT_TURN_PASSWD:
1208             cfg->media_cfg.turn_auth_cred.data.static_cred.data_type = PJ_STUN_PASSWD_PLAIN;
1209             cfg->media_cfg.turn_auth_cred.data.static_cred.data = pj_str(pj_optarg);
1210             break;
1211
1212 #if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
1213         case OPT_USE_SRTP:
1214             app_config.cfg.use_srtp = my_atoi(pj_optarg);
1215             if (!pj_isdigit(*pj_optarg) || app_config.cfg.use_srtp > 3) {
1216                 PJ_LOG(1,(THIS_FILE, "Invalid value for --use-srtp option"));
1217                 return -1;
1218             }
1219             if ((int)app_config.cfg.use_srtp == 3) {
1220                 /* SRTP optional mode with duplicated media offer */
1221                 app_config.cfg.use_srtp = PJMEDIA_SRTP_OPTIONAL;
1222                 app_config.cfg.srtp_optional_dup_offer = PJ_TRUE;
1223                 cur_acc->srtp_optional_dup_offer = PJ_TRUE;
1224             }
1225             cur_acc->use_srtp = app_config.cfg.use_srtp;
1226             break;
1227         case OPT_SRTP_SECURE:
1228             app_config.cfg.srtp_secure_signaling = my_atoi(pj_optarg);
1229             if (!pj_isdigit(*pj_optarg) || 
1230                 app_config.cfg.srtp_secure_signaling > 2) 
1231             {
1232                 PJ_LOG(1,(THIS_FILE, "Invalid value for --srtp-secure option"));
1233                 return -1;
1234             }
1235             cur_acc->srtp_secure_signaling = app_config.cfg.srtp_secure_signaling;
1236             break;
1237 #endif
1238
1239         case OPT_RTP_PORT:
1240             cfg->rtp_cfg.port = my_atoi(pj_optarg);
1241             if (cfg->rtp_cfg.port == 0) {
1242                 enum { START_PORT=4000 };
1243                 unsigned range;
1244
1245                 range = (65535-START_PORT-PJSUA_MAX_CALLS*2);
1246                 cfg->rtp_cfg.port = START_PORT + 
1247                                     ((pj_rand() % range) & 0xFFFE);
1248             }
1249
1250             if (cfg->rtp_cfg.port < 1 || cfg->rtp_cfg.port > 65535) {
1251                 PJ_LOG(1,(THIS_FILE,
1252                           "Error: rtp-port argument value "
1253                           "(expecting 1-65535"));
1254                 return -1;
1255             }
1256             break;
1257
1258         case OPT_DIS_CODEC:
1259             cfg->codec_dis[cfg->codec_dis_cnt++] = pj_str(pj_optarg);
1260             break;
1261
1262         case OPT_ADD_CODEC:
1263             cfg->codec_arg[cfg->codec_cnt++] = pj_str(pj_optarg);
1264             break;
1265
1266         /* These options were no longer valid after new pjsua */
1267         /*
1268         case OPT_COMPLEXITY:
1269             cfg->complexity = my_atoi(pj_optarg);
1270             if (cfg->complexity < 0 || cfg->complexity > 10) {
1271                 PJ_LOG(1,(THIS_FILE,
1272                           "Error: invalid --complexity (expecting 0-10"));
1273                 return -1;
1274             }
1275             break;
1276         */
1277
1278         case OPT_DURATION:
1279             cfg->duration = my_atoi(pj_optarg);
1280             break;
1281
1282         case OPT_THREAD_CNT:
1283             cfg->cfg.thread_cnt = my_atoi(pj_optarg);
1284             if (cfg->cfg.thread_cnt > 128) {
1285                 PJ_LOG(1,(THIS_FILE,
1286                           "Error: invalid --thread-cnt option"));
1287                 return -1;
1288             }
1289             break;
1290
1291         case OPT_PTIME:
1292             cfg->media_cfg.ptime = my_atoi(pj_optarg);
1293             if (cfg->media_cfg.ptime < 10 || cfg->media_cfg.ptime > 1000) {
1294                 PJ_LOG(1,(THIS_FILE,
1295                           "Error: invalid --ptime option"));
1296                 return -1;
1297             }
1298             break;
1299
1300         case OPT_NO_VAD:
1301             cfg->media_cfg.no_vad = PJ_TRUE;
1302             break;
1303
1304         case OPT_EC_TAIL:
1305             cfg->media_cfg.ec_tail_len = my_atoi(pj_optarg);
1306             if (cfg->media_cfg.ec_tail_len > 1000) {
1307                 PJ_LOG(1,(THIS_FILE, "I think the ec-tail length setting "
1308                           "is too big"));
1309                 return -1;
1310             }
1311             break;
1312
1313         case OPT_EC_OPT:
1314             cfg->media_cfg.ec_options = my_atoi(pj_optarg);
1315             break;
1316
1317         case OPT_QUALITY:
1318             cfg->media_cfg.quality = my_atoi(pj_optarg);
1319             if (cfg->media_cfg.quality < 0 || cfg->media_cfg.quality > 10) {
1320                 PJ_LOG(1,(THIS_FILE,
1321                           "Error: invalid --quality (expecting 0-10"));
1322                 return -1;
1323             }
1324             break;
1325
1326         case OPT_ILBC_MODE:
1327             cfg->media_cfg.ilbc_mode = my_atoi(pj_optarg);
1328             if (cfg->media_cfg.ilbc_mode!=20 && cfg->media_cfg.ilbc_mode!=30) {
1329                 PJ_LOG(1,(THIS_FILE,
1330                           "Error: invalid --ilbc-mode (expecting 20 or 30"));
1331                 return -1;
1332             }
1333             break;
1334
1335         case OPT_RX_DROP_PCT:
1336             cfg->media_cfg.rx_drop_pct = my_atoi(pj_optarg);
1337             if (cfg->media_cfg.rx_drop_pct > 100) {
1338                 PJ_LOG(1,(THIS_FILE,
1339                           "Error: invalid --rx-drop-pct (expecting <= 100"));
1340                 return -1;
1341             }
1342             break;
1343             
1344         case OPT_TX_DROP_PCT:
1345             cfg->media_cfg.tx_drop_pct = my_atoi(pj_optarg);
1346             if (cfg->media_cfg.tx_drop_pct > 100) {
1347                 PJ_LOG(1,(THIS_FILE,
1348                           "Error: invalid --tx-drop-pct (expecting <= 100"));
1349                 return -1;
1350             }
1351             break;
1352
1353         case OPT_AUTO_ANSWER:
1354             cfg->auto_answer = my_atoi(pj_optarg);
1355             if (cfg->auto_answer < 100 || cfg->auto_answer > 699) {
1356                 PJ_LOG(1,(THIS_FILE,
1357                           "Error: invalid code in --auto-answer "
1358                           "(expecting 100-699"));
1359                 return -1;
1360             }
1361             break;
1362
1363         case OPT_MAX_CALLS:
1364             cfg->cfg.max_calls = my_atoi(pj_optarg);
1365             if (cfg->cfg.max_calls < 1 || cfg->cfg.max_calls > PJSUA_MAX_CALLS) {
1366                 PJ_LOG(1,(THIS_FILE,"Error: maximum call setting exceeds "
1367                                     "compile time limit (PJSUA_MAX_CALLS=%d)",
1368                           PJSUA_MAX_CALLS));
1369                 return -1;
1370             }
1371             break;
1372
1373 #if defined(PJSIP_HAS_TLS_TRANSPORT) && (PJSIP_HAS_TLS_TRANSPORT != 0)
1374         case OPT_USE_TLS:
1375             cfg->use_tls = PJ_TRUE;
1376             break;
1377             
1378         case OPT_TLS_CA_FILE:
1379             cfg->udp_cfg.tls_setting.ca_list_file = pj_str(pj_optarg);
1380             break;
1381             
1382         case OPT_TLS_CERT_FILE:
1383             cfg->udp_cfg.tls_setting.cert_file = pj_str(pj_optarg);
1384             break;
1385             
1386         case OPT_TLS_PRIV_FILE:
1387             cfg->udp_cfg.tls_setting.privkey_file = pj_str(pj_optarg);
1388             break;
1389
1390         case OPT_TLS_PASSWORD:
1391             cfg->udp_cfg.tls_setting.password = pj_str(pj_optarg);
1392             break;
1393
1394         case OPT_TLS_VERIFY_SERVER:
1395             cfg->udp_cfg.tls_setting.verify_server = PJ_TRUE;
1396             break;
1397
1398         case OPT_TLS_VERIFY_CLIENT:
1399             cfg->udp_cfg.tls_setting.verify_client = PJ_TRUE;
1400             cfg->udp_cfg.tls_setting.require_client_cert = PJ_TRUE;
1401             break;
1402
1403         case OPT_TLS_NEG_TIMEOUT:
1404             cfg->udp_cfg.tls_setting.timeout.sec = atoi(pj_optarg);
1405             break;
1406
1407         case OPT_TLS_CIPHER:
1408             {
1409                 pj_ssl_cipher cipher;
1410
1411                 if (pj_ansi_strnicmp(pj_optarg, "0x", 2) == 0) {
1412                     pj_str_t cipher_st = pj_str(pj_optarg + 2);
1413                     cipher = pj_strtoul2(&cipher_st, NULL, 16);
1414                 } else {
1415                     cipher = atoi(pj_optarg);
1416                 }
1417
1418                 if (pj_ssl_cipher_is_supported(cipher)) {
1419                     static pj_ssl_cipher tls_ciphers[128];
1420
1421                     tls_ciphers[cfg->udp_cfg.tls_setting.ciphers_num++] = cipher;
1422                     cfg->udp_cfg.tls_setting.ciphers = tls_ciphers;
1423                 } else {
1424                     pj_ssl_cipher ciphers[128];
1425                     unsigned j, ciphers_cnt;
1426
1427                     ciphers_cnt = PJ_ARRAY_SIZE(ciphers);
1428                     pj_ssl_cipher_get_availables(ciphers, &ciphers_cnt);
1429                     
1430                     PJ_LOG(1,(THIS_FILE, "Cipher \"%s\" is not supported by "
1431                                          "TLS/SSL backend.", pj_optarg));
1432                     printf("Available TLS/SSL ciphers (%d):\n", ciphers_cnt);
1433                     for (j=0; j<ciphers_cnt; ++j)
1434                         printf("- 0x%06X: %s\n", ciphers[j], pj_ssl_cipher_name(ciphers[j]));
1435                     return -1;
1436                 }
1437             }
1438             break;
1439 #endif /* PJSIP_HAS_TLS_TRANSPORT */
1440
1441         case OPT_CAPTURE_DEV:
1442             cfg->capture_dev = atoi(pj_optarg);
1443             break;
1444
1445         case OPT_PLAYBACK_DEV:
1446             cfg->playback_dev = atoi(pj_optarg);
1447             break;
1448
1449         case OPT_STDOUT_REFRESH:
1450             stdout_refresh = atoi(pj_optarg);
1451             break;
1452
1453         case OPT_STDOUT_REFRESH_TEXT:
1454             stdout_refresh_text = pj_optarg;
1455             break;
1456
1457 #ifdef _IONBF
1458         case OPT_STDOUT_NO_BUF:
1459             setvbuf(stdout, NULL, _IONBF, 0);
1460             break;
1461 #endif
1462
1463         case OPT_CAPTURE_LAT:
1464             cfg->capture_lat = atoi(pj_optarg);
1465             break;
1466
1467         case OPT_PLAYBACK_LAT:
1468             cfg->playback_lat = atoi(pj_optarg);
1469             break;
1470
1471         case OPT_SND_AUTO_CLOSE:
1472             cfg->media_cfg.snd_auto_close_time = atoi(pj_optarg);
1473             break;
1474
1475         case OPT_NO_TONES:
1476             cfg->no_tones = PJ_TRUE;
1477             break;
1478
1479         case OPT_JB_MAX_SIZE:
1480             cfg->media_cfg.jb_max = atoi(pj_optarg);
1481             break;
1482
1483 #if defined(PJ_HAS_IPV6) && PJ_HAS_IPV6
1484         case OPT_IPV6:
1485             cfg->ipv6 = PJ_TRUE;
1486             break;
1487 #endif
1488         case OPT_QOS:
1489             cfg->enable_qos = PJ_TRUE;
1490             /* Set RTP traffic type to Voice */
1491             cfg->rtp_cfg.qos_type = PJ_QOS_TYPE_VOICE;
1492             /* Directly apply DSCP value to SIP traffic. Say lets
1493              * set it to CS3 (DSCP 011000). Note that this will not 
1494              * work on all platforms.
1495              */
1496             cfg->udp_cfg.qos_params.flags = PJ_QOS_PARAM_HAS_DSCP;
1497             cfg->udp_cfg.qos_params.dscp_val = 0x18;
1498             break;
1499         case OPT_VIDEO:
1500             cfg->vid.vid_cnt = 1;
1501             cfg->vid.in_auto_show = PJ_TRUE;
1502             cfg->vid.out_auto_transmit = PJ_TRUE;
1503             break;
1504         case OPT_EXTRA_AUDIO:
1505             cfg->aud_cnt++;
1506             break;
1507
1508         case OPT_VCAPTURE_DEV:
1509             cfg->vid.vcapture_dev = atoi(pj_optarg);
1510             cur_acc->vid_cap_dev = cfg->vid.vcapture_dev;
1511             break;
1512
1513         case OPT_VRENDER_DEV:
1514             cfg->vid.vrender_dev = atoi(pj_optarg);
1515             cur_acc->vid_rend_dev = cfg->vid.vrender_dev;
1516             break;
1517
1518         case OPT_PLAY_AVI:
1519             if (app_config.avi_cnt >= MAX_AVI) {
1520                 PJ_LOG(1,(THIS_FILE, "Too many AVIs"));
1521                 return -1;
1522             }
1523             app_config.avi[app_config.avi_cnt++].path = pj_str(pj_optarg);
1524             break;
1525
1526         case OPT_AUTO_PLAY_AVI:
1527             app_config.avi_auto_play = PJ_TRUE;
1528             break;
1529
1530         default:
1531             PJ_LOG(1,(THIS_FILE, 
1532                       "Argument \"%s\" is not valid. Use --help to see help",
1533                       argv[pj_optind-1]));
1534             return -1;
1535         }
1536     }
1537
1538     if (pj_optind != argc) {
1539         pj_str_t uri_arg;
1540
1541         if (pjsua_verify_url(argv[pj_optind]) != PJ_SUCCESS) {
1542             PJ_LOG(1,(THIS_FILE, "Invalid SIP URI %s", argv[pj_optind]));
1543             return -1;
1544         }
1545         uri_arg = pj_str(argv[pj_optind]);
1546         if (uri_to_call)
1547             *uri_to_call = uri_arg;
1548         pj_optind++;
1549
1550         /* Add URI to call to buddy list if it's not already there */
1551         for (i=0; i<cfg->buddy_cnt; ++i) {
1552             if (pj_stricmp(&cfg->buddy_cfg[i].uri, &uri_arg)==0)
1553                 break;
1554         }
1555         if (i == cfg->buddy_cnt && cfg->buddy_cnt < PJSUA_MAX_BUDDIES) {
1556             cfg->buddy_cfg[cfg->buddy_cnt++].uri = uri_arg;
1557         }
1558
1559     } else {
1560         if (uri_to_call)
1561             uri_to_call->slen = 0;
1562     }
1563
1564     if (pj_optind != argc) {
1565         PJ_LOG(1,(THIS_FILE, "Error: unknown options %s", argv[pj_optind]));
1566         return PJ_EINVAL;
1567     }
1568
1569     if (cfg->acc_cfg[cfg->acc_cnt].id.slen)
1570         cfg->acc_cnt++;
1571
1572     for (i=0; i<cfg->acc_cnt; ++i) {
1573         pjsua_acc_config *acfg = &cfg->acc_cfg[i];
1574
1575         if (acfg->cred_info[acfg->cred_count].username.slen)
1576         {
1577             acfg->cred_count++;
1578         }
1579
1580         /* When IMS mode is enabled for the account, verify that settings
1581          * are okay.
1582          */
1583         /* For now we check if IMS mode is activated by looking if
1584          * initial_auth is set.
1585          */
1586         if (acfg->auth_pref.initial_auth && acfg->cred_count) {
1587             /* Realm must point to the real domain */
1588             if (*acfg->cred_info[0].realm.ptr=='*') {
1589                 PJ_LOG(1,(THIS_FILE, 
1590                           "Error: cannot use '*' as realm with IMS"));
1591                 return PJ_EINVAL;
1592             }
1593
1594             /* Username for authentication must be in a@b format */
1595             if (strchr(acfg->cred_info[0].username.ptr, '@')==0) {
1596                 PJ_LOG(1,(THIS_FILE, 
1597                           "Error: Username for authentication must "
1598                           "be in user@domain format with IMS"));
1599                 return PJ_EINVAL;
1600             }
1601         }
1602     }
1603
1604
1605     return PJ_SUCCESS;
1606 }
1607
1608
1609 /*
1610  * Save account settings
1611  */
1612 static void write_account_settings(int acc_index, pj_str_t *result)
1613 {
1614     unsigned i;
1615     char line[128];
1616     pjsua_acc_config *acc_cfg = &app_config.acc_cfg[acc_index];
1617
1618     
1619     pj_ansi_sprintf(line, "\n#\n# Account %d:\n#\n", acc_index);
1620     pj_strcat2(result, line);
1621
1622
1623     /* Identity */
1624     if (acc_cfg->id.slen) {
1625         pj_ansi_sprintf(line, "--id %.*s\n", 
1626                         (int)acc_cfg->id.slen, 
1627                         acc_cfg->id.ptr);
1628         pj_strcat2(result, line);
1629     }
1630
1631     /* Registrar server */
1632     if (acc_cfg->reg_uri.slen) {
1633         pj_ansi_sprintf(line, "--registrar %.*s\n",
1634                               (int)acc_cfg->reg_uri.slen,
1635                               acc_cfg->reg_uri.ptr);
1636         pj_strcat2(result, line);
1637
1638         pj_ansi_sprintf(line, "--reg-timeout %u\n",
1639                               acc_cfg->reg_timeout);
1640         pj_strcat2(result, line);
1641     }
1642
1643     /* Contact */
1644     if (acc_cfg->force_contact.slen) {
1645         pj_ansi_sprintf(line, "--contact %.*s\n", 
1646                         (int)acc_cfg->force_contact.slen, 
1647                         acc_cfg->force_contact.ptr);
1648         pj_strcat2(result, line);
1649     }
1650
1651     /* Contact header parameters */
1652     if (acc_cfg->contact_params.slen) {
1653         pj_ansi_sprintf(line, "--contact-params %.*s\n", 
1654                         (int)acc_cfg->contact_params.slen, 
1655                         acc_cfg->contact_params.ptr);
1656         pj_strcat2(result, line);
1657     }
1658
1659     /* Contact URI parameters */
1660     if (acc_cfg->contact_uri_params.slen) {
1661         pj_ansi_sprintf(line, "--contact-uri-params %.*s\n", 
1662                         (int)acc_cfg->contact_uri_params.slen, 
1663                         acc_cfg->contact_uri_params.ptr);
1664         pj_strcat2(result, line);
1665     }
1666
1667     /*  */
1668     if (acc_cfg->allow_contact_rewrite!=1)
1669     {
1670         pj_ansi_sprintf(line, "--auto-update-nat %i\n",
1671                         (int)acc_cfg->allow_contact_rewrite);
1672         pj_strcat2(result, line);
1673     }
1674
1675 #if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
1676     /* SRTP */
1677     if (acc_cfg->use_srtp) {
1678         int use_srtp = (int)acc_cfg->use_srtp;
1679         if (use_srtp == PJMEDIA_SRTP_OPTIONAL && 
1680             acc_cfg->srtp_optional_dup_offer)
1681         {
1682             use_srtp = 3;
1683         }
1684         pj_ansi_sprintf(line, "--use-srtp %i\n", use_srtp);
1685         pj_strcat2(result, line);
1686     }
1687     if (acc_cfg->srtp_secure_signaling != 
1688         PJSUA_DEFAULT_SRTP_SECURE_SIGNALING) 
1689     {
1690         pj_ansi_sprintf(line, "--srtp-secure %d\n",
1691                         acc_cfg->srtp_secure_signaling);
1692         pj_strcat2(result, line);
1693     }
1694 #endif
1695
1696     /* Proxy */
1697     for (i=0; i<acc_cfg->proxy_cnt; ++i) {
1698         pj_ansi_sprintf(line, "--proxy %.*s\n",
1699                               (int)acc_cfg->proxy[i].slen,
1700                               acc_cfg->proxy[i].ptr);
1701         pj_strcat2(result, line);
1702     }
1703
1704     /* Credentials */
1705     for (i=0; i<acc_cfg->cred_count; ++i) {
1706         if (acc_cfg->cred_info[i].realm.slen) {
1707             pj_ansi_sprintf(line, "--realm %.*s\n",
1708                                   (int)acc_cfg->cred_info[i].realm.slen,
1709                                   acc_cfg->cred_info[i].realm.ptr);
1710             pj_strcat2(result, line);
1711         }
1712
1713         if (acc_cfg->cred_info[i].username.slen) {
1714             pj_ansi_sprintf(line, "--username %.*s\n",
1715                                   (int)acc_cfg->cred_info[i].username.slen,
1716                                   acc_cfg->cred_info[i].username.ptr);
1717             pj_strcat2(result, line);
1718         }
1719
1720         if (acc_cfg->cred_info[i].data.slen) {
1721             pj_ansi_sprintf(line, "--password %.*s\n",
1722                                   (int)acc_cfg->cred_info[i].data.slen,
1723                                   acc_cfg->cred_info[i].data.ptr);
1724             pj_strcat2(result, line);
1725         }
1726
1727         if (i != acc_cfg->cred_count - 1)
1728             pj_strcat2(result, "--next-cred\n");
1729     }
1730
1731     /* reg-use-proxy */
1732     if (acc_cfg->reg_use_proxy != 3) {
1733         pj_ansi_sprintf(line, "--reg-use-proxy %d\n",
1734                               acc_cfg->reg_use_proxy);
1735         pj_strcat2(result, line);
1736     }
1737
1738     /* rereg-delay */
1739     if (acc_cfg->reg_retry_interval != PJSUA_REG_RETRY_INTERVAL) {
1740         pj_ansi_sprintf(line, "--rereg-delay %d\n",
1741                               acc_cfg->reg_retry_interval);
1742         pj_strcat2(result, line);
1743     }
1744
1745     /* 100rel extension */
1746     if (acc_cfg->require_100rel) {
1747         pj_strcat2(result, "--use-100rel\n");
1748     }
1749
1750     /* Session Timer extension */
1751     if (acc_cfg->use_timer) {
1752         pj_ansi_sprintf(line, "--use-timer %d\n",
1753                               acc_cfg->use_timer);
1754         pj_strcat2(result, line);
1755     }
1756     if (acc_cfg->timer_setting.min_se != 90) {
1757         pj_ansi_sprintf(line, "--timer-min-se %d\n",
1758                               acc_cfg->timer_setting.min_se);
1759         pj_strcat2(result, line);
1760     }
1761     if (acc_cfg->timer_setting.sess_expires != PJSIP_SESS_TIMER_DEF_SE) {
1762         pj_ansi_sprintf(line, "--timer-se %d\n",
1763                               acc_cfg->timer_setting.sess_expires);
1764         pj_strcat2(result, line);
1765     }
1766
1767     /* Publish */
1768     if (acc_cfg->publish_enabled)
1769         pj_strcat2(result, "--publish\n");
1770
1771     /* MWI */
1772     if (acc_cfg->mwi_enabled)
1773         pj_strcat2(result, "--mwi\n");
1774 }
1775
1776
1777 /*
1778  * Write settings.
1779  */
1780 static int write_settings(const struct app_config *config,
1781                           char *buf, pj_size_t max)
1782 {
1783     unsigned acc_index;
1784     unsigned i;
1785     pj_str_t cfg;
1786     char line[128];
1787     extern pj_bool_t pjsip_use_compact_form;
1788
1789     PJ_UNUSED_ARG(max);
1790
1791     cfg.ptr = buf;
1792     cfg.slen = 0;
1793
1794     /* Logging. */
1795     pj_strcat2(&cfg, "#\n# Logging options:\n#\n");
1796     pj_ansi_sprintf(line, "--log-level %d\n",
1797                     config->log_cfg.level);
1798     pj_strcat2(&cfg, line);
1799
1800     pj_ansi_sprintf(line, "--app-log-level %d\n",
1801                     config->log_cfg.console_level);
1802     pj_strcat2(&cfg, line);
1803
1804     if (config->log_cfg.log_filename.slen) {
1805         pj_ansi_sprintf(line, "--log-file %.*s\n",
1806                         (int)config->log_cfg.log_filename.slen,
1807                         config->log_cfg.log_filename.ptr);
1808         pj_strcat2(&cfg, line);
1809     }
1810
1811     if (config->log_cfg.log_file_flags & PJ_O_APPEND) {
1812         pj_strcat2(&cfg, "--log-append\n");
1813     }
1814
1815     /* Save account settings. */
1816     for (acc_index=0; acc_index < config->acc_cnt; ++acc_index) {
1817         
1818         write_account_settings(acc_index, &cfg);
1819
1820         if (acc_index < config->acc_cnt-1)
1821             pj_strcat2(&cfg, "--next-account\n");
1822     }
1823
1824
1825     pj_strcat2(&cfg, "\n#\n# Network settings:\n#\n");
1826
1827     /* Nameservers */
1828     for (i=0; i<config->cfg.nameserver_count; ++i) {
1829         pj_ansi_sprintf(line, "--nameserver %.*s\n",
1830                               (int)config->cfg.nameserver[i].slen,
1831                               config->cfg.nameserver[i].ptr);
1832         pj_strcat2(&cfg, line);
1833     }
1834
1835     /* Outbound proxy */
1836     for (i=0; i<config->cfg.outbound_proxy_cnt; ++i) {
1837         pj_ansi_sprintf(line, "--outbound %.*s\n",
1838                               (int)config->cfg.outbound_proxy[i].slen,
1839                               config->cfg.outbound_proxy[i].ptr);
1840         pj_strcat2(&cfg, line);
1841     }
1842
1843     /* Transport options */
1844     if (config->ipv6) {
1845         pj_strcat2(&cfg, "--ipv6\n");
1846     }
1847     if (config->enable_qos) {
1848         pj_strcat2(&cfg, "--set-qos\n");
1849     }
1850
1851     /* UDP Transport. */
1852     pj_ansi_sprintf(line, "--local-port %d\n", config->udp_cfg.port);
1853     pj_strcat2(&cfg, line);
1854
1855     /* IP address, if any. */
1856     if (config->udp_cfg.public_addr.slen) {
1857         pj_ansi_sprintf(line, "--ip-addr %.*s\n", 
1858                         (int)config->udp_cfg.public_addr.slen,
1859                         config->udp_cfg.public_addr.ptr);
1860         pj_strcat2(&cfg, line);
1861     }
1862
1863     /* Bound IP address, if any. */
1864     if (config->udp_cfg.bound_addr.slen) {
1865         pj_ansi_sprintf(line, "--bound-addr %.*s\n", 
1866                         (int)config->udp_cfg.bound_addr.slen,
1867                         config->udp_cfg.bound_addr.ptr);
1868         pj_strcat2(&cfg, line);
1869     }
1870
1871     /* No TCP ? */
1872     if (config->no_tcp) {
1873         pj_strcat2(&cfg, "--no-tcp\n");
1874     }
1875
1876     /* No UDP ? */
1877     if (config->no_udp) {
1878         pj_strcat2(&cfg, "--no-udp\n");
1879     }
1880
1881     /* STUN */
1882     for (i=0; i<config->cfg.stun_srv_cnt; ++i) {
1883         pj_ansi_sprintf(line, "--stun-srv %.*s\n",
1884                         (int)config->cfg.stun_srv[i].slen, 
1885                         config->cfg.stun_srv[i].ptr);
1886         pj_strcat2(&cfg, line);
1887     }
1888
1889 #if defined(PJSIP_HAS_TLS_TRANSPORT) && (PJSIP_HAS_TLS_TRANSPORT != 0)
1890     /* TLS */
1891     if (config->use_tls)
1892         pj_strcat2(&cfg, "--use-tls\n");
1893     if (config->udp_cfg.tls_setting.ca_list_file.slen) {
1894         pj_ansi_sprintf(line, "--tls-ca-file %.*s\n",
1895                         (int)config->udp_cfg.tls_setting.ca_list_file.slen, 
1896                         config->udp_cfg.tls_setting.ca_list_file.ptr);
1897         pj_strcat2(&cfg, line);
1898     }
1899     if (config->udp_cfg.tls_setting.cert_file.slen) {
1900         pj_ansi_sprintf(line, "--tls-cert-file %.*s\n",
1901                         (int)config->udp_cfg.tls_setting.cert_file.slen, 
1902                         config->udp_cfg.tls_setting.cert_file.ptr);
1903         pj_strcat2(&cfg, line);
1904     }
1905     if (config->udp_cfg.tls_setting.privkey_file.slen) {
1906         pj_ansi_sprintf(line, "--tls-privkey-file %.*s\n",
1907                         (int)config->udp_cfg.tls_setting.privkey_file.slen, 
1908                         config->udp_cfg.tls_setting.privkey_file.ptr);
1909         pj_strcat2(&cfg, line);
1910     }
1911
1912     if (config->udp_cfg.tls_setting.password.slen) {
1913         pj_ansi_sprintf(line, "--tls-password %.*s\n",
1914                         (int)config->udp_cfg.tls_setting.password.slen, 
1915                         config->udp_cfg.tls_setting.password.ptr);
1916         pj_strcat2(&cfg, line);
1917     }
1918
1919     if (config->udp_cfg.tls_setting.verify_server)
1920         pj_strcat2(&cfg, "--tls-verify-server\n");
1921
1922     if (config->udp_cfg.tls_setting.verify_client)
1923         pj_strcat2(&cfg, "--tls-verify-client\n");
1924
1925     if (config->udp_cfg.tls_setting.timeout.sec) {
1926         pj_ansi_sprintf(line, "--tls-neg-timeout %d\n",
1927                         (int)config->udp_cfg.tls_setting.timeout.sec);
1928         pj_strcat2(&cfg, line);
1929     }
1930
1931     for (i=0; i<config->udp_cfg.tls_setting.ciphers_num; ++i) {
1932         pj_ansi_sprintf(line, "--tls-cipher 0x%06X # %s\n",
1933                         config->udp_cfg.tls_setting.ciphers[i],
1934                         pj_ssl_cipher_name(config->udp_cfg.tls_setting.ciphers[i]));
1935         pj_strcat2(&cfg, line);
1936     }
1937 #endif
1938
1939     pj_strcat2(&cfg, "\n#\n# Media settings:\n#\n");
1940
1941     /* Video & extra audio */
1942     for (i=0; i<config->vid.vid_cnt; ++i) {
1943         pj_strcat2(&cfg, "--video\n");
1944     }
1945     for (i=1; i<config->aud_cnt; ++i) {
1946         pj_strcat2(&cfg, "--extra-audio\n");
1947     }
1948
1949     /* SRTP */
1950 #if PJMEDIA_HAS_SRTP
1951     if (app_config.cfg.use_srtp != PJSUA_DEFAULT_USE_SRTP) {
1952         int use_srtp = (int)app_config.cfg.use_srtp;
1953         if (use_srtp == PJMEDIA_SRTP_OPTIONAL && 
1954             app_config.cfg.srtp_optional_dup_offer)
1955         {
1956             use_srtp = 3;
1957         }
1958         pj_ansi_sprintf(line, "--use-srtp %d\n", use_srtp);
1959         pj_strcat2(&cfg, line);
1960     }
1961     if (app_config.cfg.srtp_secure_signaling != 
1962         PJSUA_DEFAULT_SRTP_SECURE_SIGNALING) 
1963     {
1964         pj_ansi_sprintf(line, "--srtp-secure %d\n",
1965                         app_config.cfg.srtp_secure_signaling);
1966         pj_strcat2(&cfg, line);
1967     }
1968 #endif
1969
1970     /* Media Transport*/
1971     if (config->media_cfg.enable_ice)
1972         pj_strcat2(&cfg, "--use-ice\n");
1973
1974     if (config->media_cfg.ice_opt.aggressive == PJ_FALSE)
1975         pj_strcat2(&cfg, "--ice-regular\n");
1976
1977     if (config->media_cfg.enable_turn)
1978         pj_strcat2(&cfg, "--use-turn\n");
1979
1980     if (config->media_cfg.ice_max_host_cands >= 0) {
1981         pj_ansi_sprintf(line, "--ice_max_host_cands %d\n",
1982                         config->media_cfg.ice_max_host_cands);
1983         pj_strcat2(&cfg, line);
1984     }
1985
1986     if (config->media_cfg.ice_no_rtcp)
1987         pj_strcat2(&cfg, "--ice-no-rtcp\n");
1988
1989     if (config->media_cfg.turn_server.slen) {
1990         pj_ansi_sprintf(line, "--turn-srv %.*s\n",
1991                         (int)config->media_cfg.turn_server.slen,
1992                         config->media_cfg.turn_server.ptr);
1993         pj_strcat2(&cfg, line);
1994     }
1995
1996     if (config->media_cfg.turn_conn_type == PJ_TURN_TP_TCP)
1997         pj_strcat2(&cfg, "--turn-tcp\n");
1998
1999     if (config->media_cfg.turn_auth_cred.data.static_cred.username.slen) {
2000         pj_ansi_sprintf(line, "--turn-user %.*s\n",
2001                         (int)config->media_cfg.turn_auth_cred.data.static_cred.username.slen,
2002                         config->media_cfg.turn_auth_cred.data.static_cred.username.ptr);
2003         pj_strcat2(&cfg, line);
2004     }
2005
2006     if (config->media_cfg.turn_auth_cred.data.static_cred.data.slen) {
2007         pj_ansi_sprintf(line, "--turn-passwd %.*s\n",
2008                         (int)config->media_cfg.turn_auth_cred.data.static_cred.data.slen,
2009                         config->media_cfg.turn_auth_cred.data.static_cred.data.ptr);
2010         pj_strcat2(&cfg, line);
2011     }
2012
2013     /* Media */
2014     if (config->null_audio)
2015         pj_strcat2(&cfg, "--null-audio\n");
2016     if (config->auto_play)
2017         pj_strcat2(&cfg, "--auto-play\n");
2018     if (config->auto_loop)
2019         pj_strcat2(&cfg, "--auto-loop\n");
2020     if (config->auto_conf)
2021         pj_strcat2(&cfg, "--auto-conf\n");
2022     for (i=0; i<config->wav_count; ++i) {
2023         pj_ansi_sprintf(line, "--play-file %s\n",
2024                         config->wav_files[i].ptr);
2025         pj_strcat2(&cfg, line);
2026     }
2027     for (i=0; i<config->tone_count; ++i) {
2028         pj_ansi_sprintf(line, "--play-tone %d,%d,%d,%d\n",
2029                         config->tones[i].freq1, config->tones[i].freq2, 
2030                         config->tones[i].on_msec, config->tones[i].off_msec);
2031         pj_strcat2(&cfg, line);
2032     }
2033     if (config->rec_file.slen) {
2034         pj_ansi_sprintf(line, "--rec-file %s\n",
2035                         config->rec_file.ptr);
2036         pj_strcat2(&cfg, line);
2037     }
2038     if (config->auto_rec)
2039         pj_strcat2(&cfg, "--auto-rec\n");
2040     if (config->capture_dev != PJSUA_INVALID_ID) {
2041         pj_ansi_sprintf(line, "--capture-dev %d\n", config->capture_dev);
2042         pj_strcat2(&cfg, line);
2043     }
2044     if (config->playback_dev != PJSUA_INVALID_ID) {
2045         pj_ansi_sprintf(line, "--playback-dev %d\n", config->playback_dev);
2046         pj_strcat2(&cfg, line);
2047     }
2048     if (config->media_cfg.snd_auto_close_time != -1) {
2049         pj_ansi_sprintf(line, "--snd-auto-close %d\n", 
2050                         config->media_cfg.snd_auto_close_time);
2051         pj_strcat2(&cfg, line);
2052     }
2053     if (config->no_tones) {
2054         pj_strcat2(&cfg, "--no-tones\n");
2055     }
2056     if (config->media_cfg.jb_max != -1) {
2057         pj_ansi_sprintf(line, "--jb-max-size %d\n", 
2058                         config->media_cfg.jb_max);
2059         pj_strcat2(&cfg, line);
2060     }
2061
2062     /* Sound device latency */
2063     if (config->capture_lat != PJMEDIA_SND_DEFAULT_REC_LATENCY) {
2064         pj_ansi_sprintf(line, "--capture-lat %d\n", config->capture_lat);
2065         pj_strcat2(&cfg, line);
2066     }
2067     if (config->playback_lat != PJMEDIA_SND_DEFAULT_PLAY_LATENCY) {
2068         pj_ansi_sprintf(line, "--playback-lat %d\n", config->playback_lat);
2069         pj_strcat2(&cfg, line);
2070     }
2071
2072     /* Media clock rate. */
2073     if (config->media_cfg.clock_rate != PJSUA_DEFAULT_CLOCK_RATE) {
2074         pj_ansi_sprintf(line, "--clock-rate %d\n",
2075                         config->media_cfg.clock_rate);
2076         pj_strcat2(&cfg, line);
2077     } else {
2078         pj_ansi_sprintf(line, "#using default --clock-rate %d\n",
2079                         config->media_cfg.clock_rate);
2080         pj_strcat2(&cfg, line);
2081     }
2082
2083     if (config->media_cfg.snd_clock_rate && 
2084         config->media_cfg.snd_clock_rate != config->media_cfg.clock_rate) 
2085     {
2086         pj_ansi_sprintf(line, "--snd-clock-rate %d\n",
2087                         config->media_cfg.snd_clock_rate);
2088         pj_strcat2(&cfg, line);
2089     }
2090
2091     /* Stereo mode. */
2092     if (config->media_cfg.channel_count == 2) {
2093         pj_ansi_sprintf(line, "--stereo\n");
2094         pj_strcat2(&cfg, line);
2095     }
2096
2097     /* quality */
2098     if (config->media_cfg.quality != PJSUA_DEFAULT_CODEC_QUALITY) {
2099         pj_ansi_sprintf(line, "--quality %d\n",
2100                         config->media_cfg.quality);
2101         pj_strcat2(&cfg, line);
2102     } else {
2103         pj_ansi_sprintf(line, "#using default --quality %d\n",
2104                         config->media_cfg.quality);
2105         pj_strcat2(&cfg, line);
2106     }
2107
2108     if (config->vid.vcapture_dev != PJMEDIA_VID_DEFAULT_CAPTURE_DEV) {
2109         pj_ansi_sprintf(line, "--vcapture-dev %d\n", config->vid.vcapture_dev);
2110         pj_strcat2(&cfg, line);
2111     }
2112     if (config->vid.vrender_dev != PJMEDIA_VID_DEFAULT_RENDER_DEV) {
2113         pj_ansi_sprintf(line, "--vrender-dev %d\n", config->vid.vrender_dev);
2114         pj_strcat2(&cfg, line);
2115     }
2116     for (i=0; i<config->avi_cnt; ++i) {
2117         pj_ansi_sprintf(line, "--play-avi %s\n", config->avi[i].path.ptr);
2118         pj_strcat2(&cfg, line);
2119     }
2120     if (config->avi_auto_play) {
2121         pj_ansi_sprintf(line, "--auto-play-avi\n");
2122         pj_strcat2(&cfg, line);
2123     }
2124
2125     /* ptime */
2126     if (config->media_cfg.ptime) {
2127         pj_ansi_sprintf(line, "--ptime %d\n",
2128                         config->media_cfg.ptime);
2129         pj_strcat2(&cfg, line);
2130     }
2131
2132     /* no-vad */
2133     if (config->media_cfg.no_vad) {
2134         pj_strcat2(&cfg, "--no-vad\n");
2135     }
2136
2137     /* ec-tail */
2138     if (config->media_cfg.ec_tail_len != PJSUA_DEFAULT_EC_TAIL_LEN) {
2139         pj_ansi_sprintf(line, "--ec-tail %d\n",
2140                         config->media_cfg.ec_tail_len);
2141         pj_strcat2(&cfg, line);
2142     } else {
2143         pj_ansi_sprintf(line, "#using default --ec-tail %d\n",
2144                         config->media_cfg.ec_tail_len);
2145         pj_strcat2(&cfg, line);
2146     }
2147
2148     /* ec-opt */
2149     if (config->media_cfg.ec_options != 0) {
2150         pj_ansi_sprintf(line, "--ec-opt %d\n",
2151                         config->media_cfg.ec_options);
2152         pj_strcat2(&cfg, line);
2153     } 
2154
2155     /* ilbc-mode */
2156     if (config->media_cfg.ilbc_mode != PJSUA_DEFAULT_ILBC_MODE) {
2157         pj_ansi_sprintf(line, "--ilbc-mode %d\n",
2158                         config->media_cfg.ilbc_mode);
2159         pj_strcat2(&cfg, line);
2160     } else {
2161         pj_ansi_sprintf(line, "#using default --ilbc-mode %d\n",
2162                         config->media_cfg.ilbc_mode);
2163         pj_strcat2(&cfg, line);
2164     }
2165
2166     /* RTP drop */
2167     if (config->media_cfg.tx_drop_pct) {
2168         pj_ansi_sprintf(line, "--tx-drop-pct %d\n",
2169                         config->media_cfg.tx_drop_pct);
2170         pj_strcat2(&cfg, line);
2171
2172     }
2173     if (config->media_cfg.rx_drop_pct) {
2174         pj_ansi_sprintf(line, "--rx-drop-pct %d\n",
2175                         config->media_cfg.rx_drop_pct);
2176         pj_strcat2(&cfg, line);
2177
2178     }
2179
2180
2181     /* Start RTP port. */
2182     pj_ansi_sprintf(line, "--rtp-port %d\n",
2183                     config->rtp_cfg.port);
2184     pj_strcat2(&cfg, line);
2185
2186     /* Disable codec */
2187     for (i=0; i<config->codec_dis_cnt; ++i) {
2188         pj_ansi_sprintf(line, "--dis-codec %s\n",
2189                     config->codec_dis[i].ptr);
2190         pj_strcat2(&cfg, line);
2191     }
2192     /* Add codec. */
2193     for (i=0; i<config->codec_cnt; ++i) {
2194         pj_ansi_sprintf(line, "--add-codec %s\n",
2195                     config->codec_arg[i].ptr);
2196         pj_strcat2(&cfg, line);
2197     }
2198
2199     pj_strcat2(&cfg, "\n#\n# User agent:\n#\n");
2200
2201     /* Auto-answer. */
2202     if (config->auto_answer != 0) {
2203         pj_ansi_sprintf(line, "--auto-answer %d\n",
2204                         config->auto_answer);
2205         pj_strcat2(&cfg, line);
2206     }
2207
2208     /* accept-redirect */
2209     if (config->redir_op != PJSIP_REDIRECT_ACCEPT) {
2210         pj_ansi_sprintf(line, "--accept-redirect %d\n",
2211                         config->redir_op);
2212         pj_strcat2(&cfg, line);
2213     }
2214
2215     /* Max calls. */
2216     pj_ansi_sprintf(line, "--max-calls %d\n",
2217                     config->cfg.max_calls);
2218     pj_strcat2(&cfg, line);
2219
2220     /* Uas-duration. */
2221     if (config->duration != NO_LIMIT) {
2222         pj_ansi_sprintf(line, "--duration %d\n",
2223                         config->duration);
2224         pj_strcat2(&cfg, line);
2225     }
2226
2227     /* norefersub ? */
2228     if (config->no_refersub) {
2229         pj_strcat2(&cfg, "--norefersub\n");
2230     }
2231
2232     if (pjsip_use_compact_form)
2233     {
2234         pj_strcat2(&cfg, "--use-compact-form\n");
2235     }
2236
2237     if (!config->cfg.force_lr) {
2238         pj_strcat2(&cfg, "--no-force-lr\n");
2239     }
2240
2241     pj_strcat2(&cfg, "\n#\n# Buddies:\n#\n");
2242
2243     /* Add buddies. */
2244     for (i=0; i<config->buddy_cnt; ++i) {
2245         pj_ansi_sprintf(line, "--add-buddy %.*s\n",
2246                               (int)config->buddy_cfg[i].uri.slen,
2247                               config->buddy_cfg[i].uri.ptr);
2248         pj_strcat2(&cfg, line);
2249     }
2250
2251     /* SIP extensions. */
2252     pj_strcat2(&cfg, "\n#\n# SIP extensions:\n#\n");
2253     /* 100rel extension */
2254     if (config->cfg.require_100rel) {
2255         pj_strcat2(&cfg, "--use-100rel\n");
2256     }
2257     /* Session Timer extension */
2258     if (config->cfg.use_timer) {
2259         pj_ansi_sprintf(line, "--use-timer %d\n",
2260                               config->cfg.use_timer);
2261         pj_strcat2(&cfg, line);
2262     }
2263     if (config->cfg.timer_setting.min_se != 90) {
2264         pj_ansi_sprintf(line, "--timer-min-se %d\n",
2265                               config->cfg.timer_setting.min_se);
2266         pj_strcat2(&cfg, line);
2267     }
2268     if (config->cfg.timer_setting.sess_expires != PJSIP_SESS_TIMER_DEF_SE) {
2269         pj_ansi_sprintf(line, "--timer-se %d\n",
2270                               config->cfg.timer_setting.sess_expires);
2271         pj_strcat2(&cfg, line);
2272     }
2273
2274     *(cfg.ptr + cfg.slen) = '\0';
2275     return cfg.slen;
2276 }
2277
2278
2279 /*
2280  * Dump application states.
2281  */
2282 static void app_dump(pj_bool_t detail)
2283 {
2284     pjsua_dump(detail);
2285 }
2286
2287 /*
2288  * Print log of call states. Since call states may be too long for logger,
2289  * printing it is a bit tricky, it should be printed part by part as long 
2290  * as the logger can accept.
2291  */
2292 static void log_call_dump(int call_id) 
2293 {
2294     unsigned call_dump_len;
2295     unsigned part_len;
2296     unsigned part_idx;
2297     unsigned log_decor;
2298
2299     pjsua_call_dump(call_id, PJ_TRUE, some_buf, 
2300                     sizeof(some_buf), "  ");
2301     call_dump_len = strlen(some_buf);
2302
2303     log_decor = pj_log_get_decor();
2304     pj_log_set_decor(log_decor & ~(PJ_LOG_HAS_NEWLINE | PJ_LOG_HAS_CR));
2305     PJ_LOG(3,(THIS_FILE, "\n"));
2306     pj_log_set_decor(0);
2307
2308     part_idx = 0;
2309     part_len = PJ_LOG_MAX_SIZE-80;
2310     while (part_idx < call_dump_len) {
2311         char p_orig, *p;
2312
2313         p = &some_buf[part_idx];
2314         if (part_idx + part_len > call_dump_len)
2315             part_len = call_dump_len - part_idx;
2316         p_orig = p[part_len];
2317         p[part_len] = '\0';
2318         PJ_LOG(3,(THIS_FILE, "%s", p));
2319         p[part_len] = p_orig;
2320         part_idx += part_len;
2321     }
2322     pj_log_set_decor(log_decor);
2323 }
2324
2325 /*****************************************************************************
2326  * Console application
2327  */
2328
2329 static void ringback_start(pjsua_call_id call_id)
2330 {
2331     if (app_config.no_tones)
2332         return;
2333
2334     if (app_config.call_data[call_id].ringback_on)
2335         return;
2336
2337     app_config.call_data[call_id].ringback_on = PJ_TRUE;
2338
2339     if (++app_config.ringback_cnt==1 && 
2340         app_config.ringback_slot!=PJSUA_INVALID_ID) 
2341     {
2342         pjsua_conf_connect(app_config.ringback_slot, 0);
2343     }
2344 }
2345
2346 static void ring_stop(pjsua_call_id call_id)
2347 {
2348     if (app_config.no_tones)
2349         return;
2350
2351     if (app_config.call_data[call_id].ringback_on) {
2352         app_config.call_data[call_id].ringback_on = PJ_FALSE;
2353
2354         pj_assert(app_config.ringback_cnt>0);
2355         if (--app_config.ringback_cnt == 0 && 
2356             app_config.ringback_slot!=PJSUA_INVALID_ID) 
2357         {
2358             pjsua_conf_disconnect(app_config.ringback_slot, 0);
2359             pjmedia_tonegen_rewind(app_config.ringback_port);
2360         }
2361     }
2362
2363     if (app_config.call_data[call_id].ring_on) {
2364         app_config.call_data[call_id].ring_on = PJ_FALSE;
2365
2366         pj_assert(app_config.ring_cnt>0);
2367         if (--app_config.ring_cnt == 0 && 
2368             app_config.ring_slot!=PJSUA_INVALID_ID) 
2369         {
2370             pjsua_conf_disconnect(app_config.ring_slot, 0);
2371             pjmedia_tonegen_rewind(app_config.ring_port);
2372         }
2373     }
2374 }
2375
2376 static void ring_start(pjsua_call_id call_id)
2377 {
2378     if (app_config.no_tones)
2379         return;
2380
2381     if (app_config.call_data[call_id].ring_on)
2382         return;
2383
2384     app_config.call_data[call_id].ring_on = PJ_TRUE;
2385
2386     if (++app_config.ring_cnt==1 && 
2387         app_config.ring_slot!=PJSUA_INVALID_ID) 
2388     {
2389         pjsua_conf_connect(app_config.ring_slot, 0);
2390     }
2391 }
2392
2393 #ifdef HAVE_MULTIPART_TEST
2394   /*
2395    * Enable multipart in msg_data and add a dummy body into the
2396    * multipart bodies.
2397    */
2398   static void add_multipart(pjsua_msg_data *msg_data)
2399   {
2400       static pjsip_multipart_part *alt_part;
2401
2402       if (!alt_part) {
2403           pj_str_t type, subtype, content;
2404
2405           alt_part = pjsip_multipart_create_part(app_config.pool);
2406
2407           type = pj_str("text");
2408           subtype = pj_str("plain");
2409           content = pj_str("Sample text body of a multipart bodies");
2410           alt_part->body = pjsip_msg_body_create(app_config.pool, &type,
2411                                                  &subtype, &content);
2412       }
2413
2414       msg_data->multipart_ctype.type = pj_str("multipart");
2415       msg_data->multipart_ctype.subtype = pj_str("mixed");
2416       pj_list_push_back(&msg_data->multipart_parts, alt_part);
2417   }
2418 #  define TEST_MULTIPART(msg_data)      add_multipart(msg_data)
2419 #else
2420 #  define TEST_MULTIPART(msg_data)
2421 #endif
2422
2423 /*
2424  * Find next call when current call is disconnected or when user
2425  * press ']'
2426  */
2427 static pj_bool_t find_next_call(void)
2428 {
2429     int i, max;
2430
2431     max = pjsua_call_get_max_count();
2432     for (i=current_call+1; i<max; ++i) {
2433         if (pjsua_call_is_active(i)) {
2434             current_call = i;
2435             return PJ_TRUE;
2436         }
2437     }
2438
2439     for (i=0; i<current_call; ++i) {
2440         if (pjsua_call_is_active(i)) {
2441             current_call = i;
2442             return PJ_TRUE;
2443         }
2444     }
2445
2446     current_call = PJSUA_INVALID_ID;
2447     return PJ_FALSE;
2448 }
2449
2450
2451 /*
2452  * Find previous call when user press '['
2453  */
2454 static pj_bool_t find_prev_call(void)
2455 {
2456     int i, max;
2457
2458     max = pjsua_call_get_max_count();
2459     for (i=current_call-1; i>=0; --i) {
2460         if (pjsua_call_is_active(i)) {
2461             current_call = i;
2462             return PJ_TRUE;
2463         }
2464     }
2465
2466     for (i=max-1; i>current_call; --i) {
2467         if (pjsua_call_is_active(i)) {
2468             current_call = i;
2469             return PJ_TRUE;
2470         }
2471     }
2472
2473     current_call = PJSUA_INVALID_ID;
2474     return PJ_FALSE;
2475 }
2476
2477
2478 /* Callback from timer when the maximum call duration has been
2479  * exceeded.
2480  */
2481 static void call_timeout_callback(pj_timer_heap_t *timer_heap,
2482                                   struct pj_timer_entry *entry)
2483 {
2484     pjsua_call_id call_id = entry->id;
2485     pjsua_msg_data msg_data;
2486     pjsip_generic_string_hdr warn;
2487     pj_str_t hname = pj_str("Warning");
2488     pj_str_t hvalue = pj_str("399 pjsua \"Call duration exceeded\"");
2489
2490     PJ_UNUSED_ARG(timer_heap);
2491
2492     if (call_id == PJSUA_INVALID_ID) {
2493         PJ_LOG(1,(THIS_FILE, "Invalid call ID in timer callback"));
2494         return;
2495     }
2496     
2497     /* Add warning header */
2498     pjsua_msg_data_init(&msg_data);
2499     pjsip_generic_string_hdr_init2(&warn, &hname, &hvalue);
2500     pj_list_push_back(&msg_data.hdr_list, &warn);
2501
2502     /* Call duration has been exceeded; disconnect the call */
2503     PJ_LOG(3,(THIS_FILE, "Duration (%d seconds) has been exceeded "
2504                          "for call %d, disconnecting the call",
2505                          app_config.duration, call_id));
2506     entry->id = PJSUA_INVALID_ID;
2507     pjsua_call_hangup(call_id, 200, NULL, &msg_data);
2508 }
2509
2510
2511 /*
2512  * Handler when invite state has changed.
2513  */
2514 static void on_call_state(pjsua_call_id call_id, pjsip_event *e)
2515 {
2516     pjsua_call_info call_info;
2517
2518     PJ_UNUSED_ARG(e);
2519
2520     pjsua_call_get_info(call_id, &call_info);
2521
2522     if (call_info.state == PJSIP_INV_STATE_DISCONNECTED) {
2523
2524         /* Stop all ringback for this call */
2525         ring_stop(call_id);
2526
2527         /* Cancel duration timer, if any */
2528         if (app_config.call_data[call_id].timer.id != PJSUA_INVALID_ID) {
2529             struct call_data *cd = &app_config.call_data[call_id];
2530             pjsip_endpoint *endpt = pjsua_get_pjsip_endpt();
2531
2532             cd->timer.id = PJSUA_INVALID_ID;
2533             pjsip_endpt_cancel_timer(endpt, &cd->timer);
2534         }
2535
2536         /* Rewind play file when hangup automatically, 
2537          * since file is not looped
2538          */
2539         if (app_config.auto_play_hangup)
2540             pjsua_player_set_pos(app_config.wav_id, 0);
2541
2542
2543         PJ_LOG(3,(THIS_FILE, "Call %d is DISCONNECTED [reason=%d (%s)]", 
2544                   call_id,
2545                   call_info.last_status,
2546                   call_info.last_status_text.ptr));
2547
2548         if (call_id == current_call) {
2549             find_next_call();
2550         }
2551
2552         /* Dump media state upon disconnected */
2553         if (1) {
2554             PJ_LOG(5,(THIS_FILE, 
2555                       "Call %d disconnected, dumping media stats..", 
2556                       call_id));
2557             log_call_dump(call_id);
2558         }
2559
2560     } else {
2561
2562         if (app_config.duration!=NO_LIMIT && 
2563             call_info.state == PJSIP_INV_STATE_CONFIRMED) 
2564         {
2565             /* Schedule timer to hangup call after the specified duration */
2566             struct call_data *cd = &app_config.call_data[call_id];
2567             pjsip_endpoint *endpt = pjsua_get_pjsip_endpt();
2568             pj_time_val delay;
2569
2570             cd->timer.id = call_id;
2571             delay.sec = app_config.duration;
2572             delay.msec = 0;
2573             pjsip_endpt_schedule_timer(endpt, &cd->timer, &delay);
2574         }
2575
2576         if (call_info.state == PJSIP_INV_STATE_EARLY) {
2577             int code;
2578             pj_str_t reason;
2579             pjsip_msg *msg;
2580
2581             /* This can only occur because of TX or RX message */
2582             pj_assert(e->type == PJSIP_EVENT_TSX_STATE);
2583
2584             if (e->body.tsx_state.type == PJSIP_EVENT_RX_MSG) {
2585                 msg = e->body.tsx_state.src.rdata->msg_info.msg;
2586             } else {
2587                 msg = e->body.tsx_state.src.tdata->msg;
2588             }
2589
2590             code = msg->line.status.code;
2591             reason = msg->line.status.reason;
2592
2593             /* Start ringback for 180 for UAC unless there's SDP in 180 */
2594             if (call_info.role==PJSIP_ROLE_UAC && code==180 && 
2595                 msg->body == NULL && 
2596                 call_info.media_status==PJSUA_CALL_MEDIA_NONE) 
2597             {
2598                 ringback_start(call_id);
2599             }
2600
2601             PJ_LOG(3,(THIS_FILE, "Call %d state changed to %s (%d %.*s)", 
2602                       call_id, call_info.state_text.ptr,
2603                       code, (int)reason.slen, reason.ptr));
2604         } else {
2605             PJ_LOG(3,(THIS_FILE, "Call %d state changed to %s", 
2606                       call_id,
2607                       call_info.state_text.ptr));
2608         }
2609
2610         if (current_call==PJSUA_INVALID_ID)
2611             current_call = call_id;
2612
2613     }
2614 }
2615
2616
2617 /**
2618  * Handler when there is incoming call.
2619  */
2620 static void on_incoming_call(pjsua_acc_id acc_id, pjsua_call_id call_id,
2621                              pjsip_rx_data *rdata)
2622 {
2623     pjsua_call_info call_info;
2624
2625     PJ_UNUSED_ARG(acc_id);
2626     PJ_UNUSED_ARG(rdata);
2627
2628     pjsua_call_get_info(call_id, &call_info);
2629
2630     if (current_call==PJSUA_INVALID_ID)
2631         current_call = call_id;
2632
2633 #ifdef USE_GUI
2634     if (!showNotification(call_id))
2635         return;
2636 #endif
2637
2638     /* Start ringback */
2639     ring_start(call_id);
2640     
2641     if (app_config.auto_answer > 0) {
2642         pjsua_call_setting call_opt;
2643
2644         pjsua_call_setting_default(&call_opt);
2645         call_opt.aud_cnt = app_config.aud_cnt;
2646         call_opt.vid_cnt = app_config.vid.vid_cnt;
2647
2648         pjsua_call_answer2(call_id, &call_opt, app_config.auto_answer, NULL, NULL);
2649     }
2650     
2651     if (app_config.auto_answer < 200) {
2652         char notif_st[80] = {0};
2653
2654 #if PJSUA_HAS_VIDEO
2655         if (call_info.rem_offerer && call_info.rem_vid_cnt) {
2656             snprintf(notif_st, sizeof(notif_st), 
2657                      "To %s the video, type \"vid %s\" first, "
2658                      "before answering the call!\n",
2659                      (app_config.vid.vid_cnt? "reject":"accept"),
2660                      (app_config.vid.vid_cnt? "disable":"enable"));
2661         }
2662 #endif
2663
2664         PJ_LOG(3,(THIS_FILE,
2665                   "Incoming call for account %d!\n"
2666                   "Media count: %d audio & %d video\n"
2667                   "%s"
2668                   "From: %s\n"
2669                   "To: %s\n"
2670                   "Press a to answer or h to reject call",
2671                   acc_id,
2672                   call_info.rem_aud_cnt,
2673                   call_info.rem_vid_cnt,
2674                   notif_st,
2675                   call_info.remote_info.ptr,
2676                   call_info.local_info.ptr));
2677     }
2678 }
2679
2680
2681 /*
2682  * Handler when a transaction within a call has changed state.
2683  */
2684 static void on_call_tsx_state(pjsua_call_id call_id,
2685                               pjsip_transaction *tsx,
2686                               pjsip_event *e)
2687 {
2688     const pjsip_method info_method = 
2689     {
2690         PJSIP_OTHER_METHOD,
2691         { "INFO", 4 }
2692     };
2693
2694     if (pjsip_method_cmp(&tsx->method, &info_method)==0) {
2695         /*
2696          * Handle INFO method.
2697          */
2698         const pj_str_t STR_APPLICATION = { "application", 11};
2699         const pj_str_t STR_DTMF_RELAY  = { "dtmf-relay", 10 };
2700         pjsip_msg_body *body = NULL;
2701         pj_bool_t dtmf_info = PJ_FALSE;
2702         
2703         if (tsx->role == PJSIP_ROLE_UAC) {
2704             if (e->body.tsx_state.type == PJSIP_EVENT_TX_MSG)
2705                 body = e->body.tsx_state.src.tdata->msg->body;
2706             else
2707                 body = e->body.tsx_state.tsx->last_tx->msg->body;
2708         } else {
2709             if (e->body.tsx_state.type == PJSIP_EVENT_RX_MSG)
2710                 body = e->body.tsx_state.src.rdata->msg_info.msg->body;
2711         }
2712         
2713         /* Check DTMF content in the INFO message */
2714         if (body && body->len &&
2715             pj_stricmp(&body->content_type.type, &STR_APPLICATION)==0 &&
2716             pj_stricmp(&body->content_type.subtype, &STR_DTMF_RELAY)==0)
2717         {
2718             dtmf_info = PJ_TRUE;
2719         }
2720
2721         if (dtmf_info && tsx->role == PJSIP_ROLE_UAC && 
2722             (tsx->state == PJSIP_TSX_STATE_COMPLETED ||
2723                (tsx->state == PJSIP_TSX_STATE_TERMINATED &&
2724                 e->body.tsx_state.prev_state != PJSIP_TSX_STATE_COMPLETED))) 
2725         {
2726             /* Status of outgoing INFO request */
2727             if (tsx->status_code >= 200 && tsx->status_code < 300) {
2728                 PJ_LOG(4,(THIS_FILE, 
2729                           "Call %d: DTMF sent successfully with INFO",
2730                           call_id));
2731             } else if (tsx->status_code >= 300) {
2732                 PJ_LOG(4,(THIS_FILE, 
2733                           "Call %d: Failed to send DTMF with INFO: %d/%.*s",
2734                           call_id,
2735                           tsx->status_code,
2736                           (int)tsx->status_text.slen,
2737                           tsx->status_text.ptr));
2738             }
2739         } else if (dtmf_info && tsx->role == PJSIP_ROLE_UAS &&
2740                    tsx->state == PJSIP_TSX_STATE_TRYING)
2741         {
2742             /* Answer incoming INFO with 200/OK */
2743             pjsip_rx_data *rdata;
2744             pjsip_tx_data *tdata;
2745             pj_status_t status;
2746
2747             rdata = e->body.tsx_state.src.rdata;
2748
2749             if (rdata->msg_info.msg->body) {
2750                 status = pjsip_endpt_create_response(tsx->endpt, rdata,
2751                                                      200, NULL, &tdata);
2752                 if (status == PJ_SUCCESS)
2753                     status = pjsip_tsx_send_msg(tsx, tdata);
2754
2755                 PJ_LOG(3,(THIS_FILE, "Call %d: incoming INFO:\n%.*s", 
2756                           call_id,
2757                           (int)rdata->msg_info.msg->body->len,
2758                           rdata->msg_info.msg->body->data));
2759             } else {
2760                 status = pjsip_endpt_create_response(tsx->endpt, rdata,
2761                                                      400, NULL, &tdata);
2762                 if (status == PJ_SUCCESS)
2763                     status = pjsip_tsx_send_msg(tsx, tdata);
2764             }
2765         }
2766     }
2767 }
2768
2769 /* General processing for media state. "mi" is the media index */
2770 static void on_call_generic_media_state(pjsua_call_info *ci, unsigned mi,
2771                                         pj_bool_t *has_error)
2772 {
2773     const char *status_name[] = {
2774         "None",
2775         "Active",
2776         "Local hold",
2777         "Remote hold",
2778         "Error"
2779     };
2780
2781     PJ_UNUSED_ARG(has_error);
2782
2783     pj_assert(ci->media[mi].status <= PJ_ARRAY_SIZE(status_name));
2784     pj_assert(PJSUA_CALL_MEDIA_ERROR == 4);
2785
2786     PJ_LOG(4,(THIS_FILE, "Call %d media %d [type=%s], status is %s",
2787               ci->id, mi, pjmedia_type_name(ci->media[mi].type),
2788               status_name[ci->media[mi].status]));
2789 }
2790
2791 /* Process audio media state. "mi" is the media index. */
2792 static void on_call_audio_state(pjsua_call_info *ci, unsigned mi,
2793                                 pj_bool_t *has_error)
2794 {
2795     PJ_UNUSED_ARG(has_error);
2796
2797     /* Stop ringback */
2798     ring_stop(ci->id);
2799
2800     /* Connect ports appropriately when media status is ACTIVE or REMOTE HOLD,
2801      * otherwise we should NOT connect the ports.
2802      */
2803     if (ci->media[mi].status == PJSUA_CALL_MEDIA_ACTIVE ||
2804         ci->media[mi].status == PJSUA_CALL_MEDIA_REMOTE_HOLD)
2805     {
2806         pj_bool_t connect_sound = PJ_TRUE;
2807         pj_bool_t disconnect_mic = PJ_FALSE;
2808         pjsua_conf_port_id call_conf_slot;
2809
2810         call_conf_slot = ci->media[mi].stream.aud.conf_slot;
2811
2812         /* Loopback sound, if desired */
2813         if (app_config.auto_loop) {
2814             pjsua_conf_connect(call_conf_slot, call_conf_slot);
2815             connect_sound = PJ_FALSE;
2816         }
2817
2818         /* Automatically record conversation, if desired */
2819         if (app_config.auto_rec && app_config.rec_port != PJSUA_INVALID_ID) {
2820             pjsua_conf_connect(call_conf_slot, app_config.rec_port);
2821         }
2822
2823         /* Stream a file, if desired */
2824         if ((app_config.auto_play || app_config.auto_play_hangup) && 
2825             app_config.wav_port != PJSUA_INVALID_ID)
2826         {
2827             pjsua_conf_connect(app_config.wav_port, call_conf_slot);
2828             connect_sound = PJ_FALSE;
2829         }
2830
2831         /* Stream AVI, if desired */
2832         if (app_config.avi_auto_play &&
2833             app_config.avi_def_idx != PJSUA_INVALID_ID &&
2834             app_config.avi[app_config.avi_def_idx].slot != PJSUA_INVALID_ID)
2835         {
2836             pjsua_conf_connect(app_config.avi[app_config.avi_def_idx].slot,
2837                                call_conf_slot);
2838             disconnect_mic = PJ_TRUE;
2839         }
2840
2841         /* Put call in conference with other calls, if desired */
2842         if (app_config.auto_conf) {
2843             pjsua_call_id call_ids[PJSUA_MAX_CALLS];
2844             unsigned call_cnt=PJ_ARRAY_SIZE(call_ids);
2845             unsigned i;
2846
2847             /* Get all calls, and establish media connection between
2848              * this call and other calls.
2849              */
2850             pjsua_enum_calls(call_ids, &call_cnt);
2851
2852             for (i=0; i<call_cnt; ++i) {
2853                 if (call_ids[i] == ci->id)
2854                     continue;
2855                 
2856                 if (!pjsua_call_has_media(call_ids[i]))
2857                     continue;
2858
2859                 pjsua_conf_connect(call_conf_slot,
2860                                    pjsua_call_get_conf_port(call_ids[i]));
2861                 pjsua_conf_connect(pjsua_call_get_conf_port(call_ids[i]),
2862                                    call_conf_slot);
2863
2864                 /* Automatically record conversation, if desired */
2865                 if (app_config.auto_rec && app_config.rec_port != PJSUA_INVALID_ID) {
2866                     pjsua_conf_connect(pjsua_call_get_conf_port(call_ids[i]), 
2867                                        app_config.rec_port);
2868                 }
2869
2870             }
2871
2872             /* Also connect call to local sound device */
2873             connect_sound = PJ_TRUE;
2874         }
2875
2876         /* Otherwise connect to sound device */
2877         if (connect_sound) {
2878             pjsua_conf_connect(call_conf_slot, 0);
2879             if (!disconnect_mic)
2880                 pjsua_conf_connect(0, call_conf_slot);
2881
2882             /* Automatically record conversation, if desired */
2883             if (app_config.auto_rec && app_config.rec_port != PJSUA_INVALID_ID) {
2884                 pjsua_conf_connect(call_conf_slot, app_config.rec_port);
2885                 pjsua_conf_connect(0, app_config.rec_port);
2886             }
2887         }
2888     }
2889 }
2890
2891 /* arrange windows. arg:
2892  *   -1:    arrange all windows
2893  *   != -1: arrange only this window id
2894  */
2895 static void arrange_window(pjsua_vid_win_id wid)
2896 {
2897 #if PJSUA_HAS_VIDEO
2898     pjmedia_coord pos;
2899     int i, last;
2900
2901     pos.x = 0;
2902     pos.y = 10;
2903     last = (wid == PJSUA_INVALID_ID) ? PJSUA_MAX_VID_WINS : wid;
2904
2905     for (i=0; i<last; ++i) {
2906         pjsua_vid_win_info wi;
2907         pj_status_t status;
2908
2909         status = pjsua_vid_win_get_info(i, &wi);
2910         if (status != PJ_SUCCESS)
2911             continue;
2912
2913         if (wid == PJSUA_INVALID_ID)
2914             pjsua_vid_win_set_pos(i, &pos);
2915
2916         if (wi.show)
2917             pos.y += wi.size.h;
2918     }
2919
2920     if (wid != PJSUA_INVALID_ID)
2921         pjsua_vid_win_set_pos(wid, &pos);
2922 #else
2923     PJ_UNUSED_ARG(wid);
2924 #endif
2925 }
2926
2927 /* Process video media state. "mi" is the media index. */
2928 static void on_call_video_state(pjsua_call_info *ci, unsigned mi,
2929                                 pj_bool_t *has_error)
2930 {
2931     if (ci->media_status != PJSUA_CALL_MEDIA_ACTIVE)
2932         return;
2933
2934     arrange_window(ci->media[mi].stream.vid.win_in);
2935
2936     PJ_UNUSED_ARG(has_error);
2937 }
2938
2939 /*
2940  * Callback on media state changed event.
2941  * The action may connect the call to sound device, to file, or
2942  * to loop the call.
2943  */
2944 static void on_call_media_state(pjsua_call_id call_id)
2945 {
2946     pjsua_call_info call_info;
2947     unsigned mi;
2948     pj_bool_t has_error = PJ_FALSE;
2949
2950     pjsua_call_get_info(call_id, &call_info);
2951
2952     for (mi=0; mi<call_info.media_cnt; ++mi) {
2953         on_call_generic_media_state(&call_info, mi, &has_error);
2954
2955         switch (call_info.media[mi].type) {
2956         case PJMEDIA_TYPE_AUDIO:
2957             on_call_audio_state(&call_info, mi, &has_error);
2958             break;
2959         case PJMEDIA_TYPE_VIDEO:
2960             on_call_video_state(&call_info, mi, &has_error);
2961             break;
2962         default:
2963             /* Make gcc happy about enum not handled by switch/case */
2964             break;
2965         }
2966     }
2967
2968     if (has_error) {
2969         pj_str_t reason = pj_str("Media failed");
2970         pjsua_call_hangup(call_id, 500, &reason, NULL);
2971     }
2972
2973 #if PJSUA_HAS_VIDEO
2974     /* Check if remote has just tried to enable video */
2975     if (call_info.rem_offerer && call_info.rem_vid_cnt)
2976     {
2977         int vid_idx;
2978
2979         /* Check if there is active video */
2980         vid_idx = pjsua_call_get_vid_stream_idx(call_id);
2981         if (vid_idx == -1 || call_info.media[vid_idx].dir == PJMEDIA_DIR_NONE) {
2982             PJ_LOG(3,(THIS_FILE,
2983                       "Just rejected incoming video offer on call %d, "
2984                       "use \"vid call enable %d\" or \"vid call add\" to enable video!",
2985                       call_id, vid_idx));
2986         }
2987     }
2988 #endif
2989 }
2990
2991 /*
2992  * DTMF callback.
2993  */
2994 static void call_on_dtmf_callback(pjsua_call_id call_id, int dtmf)
2995 {
2996     PJ_LOG(3,(THIS_FILE, "Incoming DTMF on call %d: %c", call_id, dtmf));
2997 }
2998
2999 /*
3000  * Redirection handler.
3001  */
3002 static pjsip_redirect_op call_on_redirected(pjsua_call_id call_id, 
3003                                             const pjsip_uri *target,
3004                                             const pjsip_event *e)
3005 {
3006     PJ_UNUSED_ARG(e);
3007
3008     if (app_config.redir_op == PJSIP_REDIRECT_PENDING) {
3009         char uristr[PJSIP_MAX_URL_SIZE];
3010         int len;
3011
3012         len = pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, target, uristr, 
3013                               sizeof(uristr));
3014         if (len < 1) {
3015             pj_ansi_strcpy(uristr, "--URI too long--");
3016         }
3017
3018         PJ_LOG(3,(THIS_FILE, "Call %d is being redirected to %.*s. "
3019                   "Press 'Ra' to accept, 'Rr' to reject, or 'Rd' to "
3020                   "disconnect.",
3021                   call_id, len, uristr));
3022     }
3023
3024     return app_config.redir_op;
3025 }
3026
3027 /*
3028  * Handler registration status has changed.
3029  */
3030 static void on_reg_state(pjsua_acc_id acc_id)
3031 {
3032     PJ_UNUSED_ARG(acc_id);
3033
3034     // Log already written.
3035 }
3036
3037
3038 /*
3039  * Handler for incoming presence subscription request
3040  */
3041 static void on_incoming_subscribe(pjsua_acc_id acc_id,
3042                                   pjsua_srv_pres *srv_pres,
3043                                   pjsua_buddy_id buddy_id,
3044                                   const pj_str_t *from,
3045                                   pjsip_rx_data *rdata,
3046                                   pjsip_status_code *code,
3047                                   pj_str_t *reason,
3048                                   pjsua_msg_data *msg_data)
3049 {
3050     /* Just accept the request (the default behavior) */
3051     PJ_UNUSED_ARG(acc_id);
3052     PJ_UNUSED_ARG(srv_pres);
3053     PJ_UNUSED_ARG(buddy_id);
3054     PJ_UNUSED_ARG(from);
3055     PJ_UNUSED_ARG(rdata);
3056     PJ_UNUSED_ARG(code);
3057     PJ_UNUSED_ARG(reason);
3058     PJ_UNUSED_ARG(msg_data);
3059 }
3060
3061
3062 /*
3063  * Handler on buddy state changed.
3064  */
3065 static void on_buddy_state(pjsua_buddy_id buddy_id)
3066 {
3067     pjsua_buddy_info info;
3068     pjsua_buddy_get_info(buddy_id, &info);
3069
3070     PJ_LOG(3,(THIS_FILE, "%.*s status is %.*s, subscription state is %s "
3071                          "(last termination reason code=%d %.*s)",
3072               (int)info.uri.slen,
3073               info.uri.ptr,