BLOG | DOCUMENTATION | TRAC

Home --> Documentations --> PJSIP Reference

PJSUA

This is the reference implementation for PJSIP and PJMEDIA. PJSUA is a console based application, designed to be simple enough to be readble, but powerful enough to demonstrate all features available in PJSIP and PJMEDIA.

This file is pjsip-apps/src/pjsua/pjsua_app.c

Screenshot on WinXP:

pjsua.jpg

pjsua on WinXP

00001 /* $Id: pjsua_app.c 4129 2012-05-17 08:27:46Z nanang $ */
00002 /* 
00003  * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
00004  * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
00005  *
00006  * This program is free software; you can redistribute it and/or modify
00007  * it under the terms of the GNU General Public License as published by
00008  * the Free Software Foundation; either version 2 of the License, or
00009  * (at your option) any later version.
00010  *
00011  * This program is distributed in the hope that it will be useful,
00012  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00013  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00014  * GNU General Public License for more details.
00015  *
00016  * You should have received a copy of the GNU General Public License
00017  * along with this program; if not, write to the Free Software
00018  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
00019  */
00020 #include <pjsua-lib/pjsua.h>
00021 #include "gui.h"
00022 
00023 
00024 #define THIS_FILE       "pjsua_app.c"
00025 #define NO_LIMIT        (int)0x7FFFFFFF
00026 
00027 //#define STEREO_DEMO
00028 //#define TRANSPORT_ADAPTER_SAMPLE
00029 //#define HAVE_MULTIPART_TEST
00030 
00031 /* Ringtones                US         UK  */
00032 #define RINGBACK_FREQ1      440     /* 400 */
00033 #define RINGBACK_FREQ2      480     /* 450 */
00034 #define RINGBACK_ON         2000    /* 400 */
00035 #define RINGBACK_OFF        4000    /* 200 */
00036 #define RINGBACK_CNT        1       /* 2   */
00037 #define RINGBACK_INTERVAL   4000    /* 2000 */
00038 
00039 #define RING_FREQ1          800
00040 #define RING_FREQ2          640
00041 #define RING_ON             200
00042 #define RING_OFF            100
00043 #define RING_CNT            3
00044 #define RING_INTERVAL       3000
00045 
00046 #define MAX_AVI             4
00047 
00048 /* Call specific data */
00049 struct call_data
00050 {
00051     pj_timer_entry          timer;
00052     pj_bool_t               ringback_on;
00053     pj_bool_t               ring_on;
00054 };
00055 
00056 /* Video settings */
00057 struct app_vid
00058 {
00059     unsigned                vid_cnt;
00060     int                     vcapture_dev;
00061     int                     vrender_dev;
00062     pj_bool_t               in_auto_show;
00063     pj_bool_t               out_auto_transmit;
00064 };
00065 
00066 /* Pjsua application data */
00067 static struct app_config
00068 {
00069     pjsua_config            cfg;
00070     pjsua_logging_config    log_cfg;
00071     pjsua_media_config      media_cfg;
00072     pj_bool_t               no_refersub;
00073     pj_bool_t               ipv6;
00074     pj_bool_t               enable_qos;
00075     pj_bool_t               no_tcp;
00076     pj_bool_t               no_udp;
00077     pj_bool_t               use_tls;
00078     pjsua_transport_config  udp_cfg;
00079     pjsua_transport_config  rtp_cfg;
00080     pjsip_redirect_op       redir_op;
00081 
00082     unsigned                acc_cnt;
00083     pjsua_acc_config        acc_cfg[PJSUA_MAX_ACC];
00084 
00085     unsigned                buddy_cnt;
00086     pjsua_buddy_config      buddy_cfg[PJSUA_MAX_BUDDIES];
00087 
00088     struct call_data        call_data[PJSUA_MAX_CALLS];
00089 
00090     pj_pool_t              *pool;
00091     /* Compatibility with older pjsua */
00092 
00093     unsigned                codec_cnt;
00094     pj_str_t                codec_arg[32];
00095     unsigned                codec_dis_cnt;
00096     pj_str_t                codec_dis[32];
00097     pj_bool_t               null_audio;
00098     unsigned                wav_count;
00099     pj_str_t                wav_files[32];
00100     unsigned                tone_count;
00101     pjmedia_tone_desc       tones[32];
00102     pjsua_conf_port_id      tone_slots[32];
00103     pjsua_player_id         wav_id;
00104     pjsua_conf_port_id      wav_port;
00105     pj_bool_t               auto_play;
00106     pj_bool_t               auto_play_hangup;
00107     pj_timer_entry          auto_hangup_timer;
00108     pj_bool_t               auto_loop;
00109     pj_bool_t               auto_conf;
00110     pj_str_t                rec_file;
00111     pj_bool_t               auto_rec;
00112     pjsua_recorder_id       rec_id;
00113     pjsua_conf_port_id      rec_port;
00114     unsigned                auto_answer;
00115     unsigned                duration;
00116 
00117 #ifdef STEREO_DEMO
00118     pjmedia_snd_port       *snd;
00119     pjmedia_port           *sc, *sc_ch1;
00120     pjsua_conf_port_id      sc_ch1_slot;
00121 #endif
00122 
00123     float                   mic_level,
00124                             speaker_level;
00125 
00126     int                     capture_dev, playback_dev;
00127     unsigned                capture_lat, playback_lat;
00128 
00129     pj_bool_t               no_tones;
00130     int                     ringback_slot;
00131     int                     ringback_cnt;
00132     pjmedia_port           *ringback_port;
00133     int                     ring_slot;
00134     int                     ring_cnt;
00135     pjmedia_port           *ring_port;
00136 
00137     struct app_vid          vid;
00138     unsigned                aud_cnt;
00139 
00140     /* AVI to play */
00141     unsigned                avi_cnt;
00142     struct {
00143         pj_str_t                path;
00144         pjmedia_vid_dev_index   dev_id;
00145         pjsua_conf_port_id      slot;
00146     } avi[MAX_AVI];
00147     pj_bool_t               avi_auto_play;
00148     int                     avi_def_idx;
00149 
00150 } app_config;
00151 
00152 
00153 //static pjsua_acc_id   current_acc;
00154 #define current_acc     pjsua_acc_get_default()
00155 static pjsua_call_id    current_call = PJSUA_INVALID_ID;
00156 static pj_bool_t        cmd_echo;
00157 static int              stdout_refresh = -1;
00158 static const char      *stdout_refresh_text = "STDOUT_REFRESH";
00159 static pj_bool_t        stdout_refresh_quit = PJ_FALSE;
00160 static pj_str_t         uri_arg;
00161 
00162 #if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0)
00163 #   define SOME_BUF_SIZE        (1024 * 10)
00164 #else
00165 #   define SOME_BUF_SIZE        (1024 * 3)
00166 #endif
00167 
00168 static char some_buf[SOME_BUF_SIZE];
00169 
00170 #ifdef STEREO_DEMO
00171 static void stereo_demo();
00172 #endif
00173 static pj_status_t create_ipv6_media_transports(void);
00174 pj_status_t app_destroy(void);
00175 
00176 static void ringback_start(pjsua_call_id call_id);
00177 static void ring_start(pjsua_call_id call_id);
00178 static void ring_stop(pjsua_call_id call_id);
00179 
00180 pj_bool_t       app_restart;
00181 pj_log_func     *log_cb = NULL;
00182 
00183 /*****************************************************************************
00184  * Configuration manipulation
00185  */
00186 
00187 #if (defined(PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT) && \
00188     PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT!=0) || \
00189     defined(__IPHONE_4_0)
00190 void keepAliveFunction(int timeout)
00191 {
00192     int i;
00193     for (i=0; i<(int)pjsua_acc_get_count(); ++i) {
00194         if (!pjsua_acc_is_valid(i))
00195             continue;
00196 
00197         if (app_config.acc_cfg[i].reg_timeout < timeout)
00198             app_config.acc_cfg[i].reg_timeout = timeout;
00199         pjsua_acc_set_registration(i, PJ_TRUE);
00200     }
00201 }
00202 #endif
00203 
00204 /* Show usage */
00205 static void usage(void)
00206 {
00207     puts  ("Usage:");
00208     puts  ("  pjsua [options] [SIP URL to call]");
00209     puts  ("");
00210     puts  ("General options:");
00211     puts  ("  --config-file=file  Read the config/arguments from file.");
00212     puts  ("  --help              Display this help screen");
00213     puts  ("  --version           Display version info");
00214     puts  ("");
00215     puts  ("Logging options:");
00216     puts  ("  --log-file=fname    Log to filename (default stderr)");
00217     puts  ("  --log-level=N       Set log max level to N (0(none) to 6(trace)) (default=5)");
00218     puts  ("  --app-log-level=N   Set log max level for stdout display (default=4)");
00219     puts  ("  --log-append        Append instead of overwrite existing log file.\n");
00220     puts  ("  --color             Use colorful logging (default yes on Win32)");
00221     puts  ("  --no-color          Disable colorful logging");
00222     puts  ("  --light-bg          Use dark colors for light background (default is dark bg)");
00223     puts  ("  --no-stderr         Disable stderr");
00224 
00225     puts  ("");
00226     puts  ("SIP Account options:");
00227     puts  ("  --use-ims           Enable 3GPP/IMS related settings on this account");
00228 #if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
00229     puts  ("  --use-srtp=N        Use SRTP?  0:disabled, 1:optional, 2:mandatory,");
00230     puts  ("                      3:optional by duplicating media offer (def:0)");
00231     puts  ("  --srtp-secure=N     SRTP require secure SIP? 0:no, 1:tls, 2:sips (def:1)");
00232 #endif
00233     puts  ("  --registrar=url     Set the URL of registrar server");
00234     puts  ("  --id=url            Set the URL of local ID (used in From header)");
00235     puts  ("  --contact=url       Optionally override the Contact information");
00236     puts  ("  --contact-params=S  Append the specified parameters S in Contact header");
00237     puts  ("  --contact-uri-params=S  Append the specified parameters S in Contact URI");
00238     puts  ("  --proxy=url         Optional URL of proxy server to visit");
00239     puts  ("                      May be specified multiple times");
00240     printf("  --reg-timeout=SEC   Optional registration interval (default %d)\n",
00241             PJSUA_REG_INTERVAL);
00242     printf("  --rereg-delay=SEC   Optional auto retry registration interval (default %d)\n",
00243             PJSUA_REG_RETRY_INTERVAL);
00244     puts  ("  --reg-use-proxy=N   Control the use of proxy settings in REGISTER.");
00245     puts  ("                      0=no proxy, 1=outbound only, 2=acc only, 3=all (default)");
00246     puts  ("  --realm=string      Set realm");
00247     puts  ("  --username=string   Set authentication username");
00248     puts  ("  --password=string   Set authentication password");
00249     puts  ("  --publish           Send presence PUBLISH for this account");
00250     puts  ("  --mwi               Subscribe to message summary/waiting indication");
00251     puts  ("  --use-100rel        Require reliable provisional response (100rel)");
00252     puts  ("  --use-timer=N       Use SIP session timers? (default=1)");
00253     puts  ("                      0:inactive, 1:optional, 2:mandatory, 3:always");
00254     printf("  --timer-se=N        Session timers expiration period, in secs (def:%d)\n",
00255             PJSIP_SESS_TIMER_DEF_SE);
00256     puts  ("  --timer-min-se=N    Session timers minimum expiration period, in secs (def:90)");
00257     puts  ("  --outb-rid=string   Set SIP outbound reg-id (default:1)");
00258     puts  ("  --auto-update-nat=N Where N is 0 or 1 to enable/disable SIP traversal behind");
00259     puts  ("                      symmetric NAT (default 1)");
00260     puts  ("  --next-cred         Add another credentials");
00261     puts  ("");
00262     puts  ("SIP Account Control:");
00263     puts  ("  --next-account      Add more account");
00264     puts  ("");
00265     puts  ("Transport Options:");
00266 #if defined(PJ_HAS_IPV6) && PJ_HAS_IPV6
00267     puts  ("  --ipv6              Use IPv6 instead for SIP and media.");
00268 #endif
00269     puts  ("  --set-qos           Enable QoS tagging for SIP and media.");
00270     puts  ("  --local-port=port   Set TCP/UDP port. This implicitly enables both ");
00271     puts  ("                      TCP and UDP transports on the specified port, unless");
00272     puts  ("                      if TCP or UDP is disabled.");
00273     puts  ("  --ip-addr=IP        Use the specifed address as SIP and RTP addresses.");
00274     puts  ("                      (Hint: the IP may be the public IP of the NAT/router)");
00275     puts  ("  --bound-addr=IP     Bind transports to this IP interface");
00276     puts  ("  --no-tcp            Disable TCP transport.");
00277     puts  ("  --no-udp            Disable UDP transport.");
00278     puts  ("  --nameserver=NS     Add the specified nameserver to enable SRV resolution");
00279     puts  ("                      This option can be specified multiple times.");
00280     puts  ("  --outbound=url      Set the URL of global outbound proxy server");
00281     puts  ("                      May be specified multiple times");
00282     puts  ("  --stun-srv=FORMAT   Set STUN server host or domain. This option may be");
00283     puts  ("                      specified more than once. FORMAT is hostdom[:PORT]");
00284 
00285 #if defined(PJSIP_HAS_TLS_TRANSPORT) && (PJSIP_HAS_TLS_TRANSPORT != 0)
00286     puts  ("");
00287     puts  ("TLS Options:");
00288     puts  ("  --use-tls           Enable TLS transport (default=no)");
00289     puts  ("  --tls-ca-file       Specify TLS CA file (default=none)");
00290     puts  ("  --tls-cert-file     Specify TLS certificate file (default=none)");
00291     puts  ("  --tls-privkey-file  Specify TLS private key file (default=none)");
00292     puts  ("  --tls-password      Specify TLS password to private key file (default=none)");
00293     puts  ("  --tls-verify-server Verify server's certificate (default=no)");
00294     puts  ("  --tls-verify-client Verify client's certificate (default=no)");
00295     puts  ("  --tls-neg-timeout   Specify TLS negotiation timeout (default=no)");
00296     puts  ("  --tls-srv-name      Specify TLS server name for multihosting server");
00297     puts  ("  --tls-cipher        Specify prefered TLS cipher (optional).");
00298     puts  ("                      May be specified multiple times");
00299 #endif
00300 
00301     puts  ("");
00302     puts  ("Audio Options:");
00303     puts  ("  --add-codec=name    Manually add codec (default is to enable all)");
00304     puts  ("  --dis-codec=name    Disable codec (can be specified multiple times)");
00305     puts  ("  --clock-rate=N      Override conference bridge clock rate");
00306     puts  ("  --snd-clock-rate=N  Override sound device clock rate");
00307     puts  ("  --stereo            Audio device and conference bridge opened in stereo mode");
00308     puts  ("  --null-audio        Use NULL audio device");
00309     puts  ("  --play-file=file    Register WAV file in conference bridge.");
00310     puts  ("                      This can be specified multiple times.");
00311     puts  ("  --play-tone=FORMAT  Register tone to the conference bridge.");
00312     puts  ("                      FORMAT is 'F1,F2,ON,OFF', where F1,F2 are");
00313     puts  ("                      frequencies, and ON,OFF=on/off duration in msec.");
00314     puts  ("                      This can be specified multiple times.");
00315     puts  ("  --auto-play         Automatically play the file (to incoming calls only)");
00316     puts  ("  --auto-loop         Automatically loop incoming RTP to outgoing RTP");
00317     puts  ("  --auto-conf         Automatically put calls in conference with others");
00318     puts  ("  --rec-file=file     Open file recorder (extension can be .wav or .mp3");
00319     puts  ("  --auto-rec          Automatically record conversation");
00320     puts  ("  --quality=N         Specify media quality (0-10, default=6)");
00321     puts  ("  --ptime=MSEC        Override codec ptime to MSEC (default=specific)");
00322     puts  ("  --no-vad            Disable VAD/silence detector (default=vad enabled)");
00323     puts  ("  --ec-tail=MSEC      Set echo canceller tail length (default=256)");
00324     puts  ("  --ec-opt=OPT        Select echo canceller algorithm (0=default, ");
00325     puts  ("                        1=speex, 2=suppressor)");
00326     puts  ("  --ilbc-mode=MODE    Set iLBC codec mode (20 or 30, default is 30)");
00327     puts  ("  --capture-dev=id    Audio capture device ID (default=-1)");
00328     puts  ("  --playback-dev=id   Audio playback device ID (default=-1)");
00329     puts  ("  --capture-lat=N     Audio capture latency, in ms (default=100)");
00330     puts  ("  --playback-lat=N    Audio playback latency, in ms (default=100)");
00331     puts  ("  --snd-auto-close=N  Auto close audio device when idle for N secs (default=1)");
00332     puts  ("                      Specify N=-1 to disable this feature.");
00333     puts  ("                      Specify N=0 for instant close when unused.");
00334     puts  ("  --no-tones          Disable audible tones");
00335     puts  ("  --jb-max-size       Specify jitter buffer maximum size, in frames (default=-1)");
00336     puts  ("  --extra-audio       Add one more audio stream");
00337 
00338 #if PJSUA_HAS_VIDEO
00339     puts  ("");
00340     puts  ("Video Options:");
00341     puts  ("  --video             Enable video");
00342     puts  ("  --vcapture-dev=id   Video capture device ID (default=-1)");
00343     puts  ("  --vrender-dev=id    Video render device ID (default=-1)");
00344     puts  ("  --play-avi=FILE     Load this AVI as virtual capture device");
00345     puts  ("  --auto-play-avi     Automatically play the AVI media to call");
00346 #endif
00347 
00348     puts  ("");
00349     puts  ("Media Transport Options:");
00350     puts  ("  --use-ice           Enable ICE (default:no)");
00351     puts  ("  --ice-regular       Use ICE regular nomination (default: aggressive)");
00352     puts  ("  --ice-max-hosts=N   Set maximum number of ICE host candidates");
00353     puts  ("  --ice-no-rtcp       Disable RTCP component in ICE (default: no)");
00354     puts  ("  --rtp-port=N        Base port to try for RTP (default=4000)");
00355     puts  ("  --rx-drop-pct=PCT   Drop PCT percent of RX RTP (for pkt lost sim, default: 0)");
00356     puts  ("  --tx-drop-pct=PCT   Drop PCT percent of TX RTP (for pkt lost sim, default: 0)");
00357     puts  ("  --use-turn          Enable TURN relay with ICE (default:no)");
00358     puts  ("  --turn-srv          Domain or host name of TURN server (\"NAME:PORT\" format)");
00359     puts  ("  --turn-tcp          Use TCP connection to TURN server (default no)");
00360     puts  ("  --turn-user         TURN username");
00361     puts  ("  --turn-passwd       TURN password");
00362 
00363     puts  ("");
00364     puts  ("Buddy List (can be more than one):");
00365     puts  ("  --add-buddy url     Add the specified URL to the buddy list.");
00366     puts  ("");
00367     puts  ("User Agent options:");
00368     puts  ("  --auto-answer=code  Automatically answer incoming calls with code (e.g. 200)");
00369     puts  ("  --max-calls=N       Maximum number of concurrent calls (default:4, max:255)");
00370     puts  ("  --thread-cnt=N      Number of worker threads (default:1)");
00371     puts  ("  --duration=SEC      Set maximum call duration (default:no limit)");
00372     puts  ("  --norefersub        Suppress event subscription when transfering calls");
00373     puts  ("  --use-compact-form  Minimize SIP message size");
00374     puts  ("  --no-force-lr       Allow strict-route to be used (i.e. do not force lr)");
00375     puts  ("  --accept-redirect=N Specify how to handle call redirect (3xx) response.");
00376     puts  ("                      0: reject, 1: follow automatically (default), 2: ask");
00377 
00378     puts  ("");
00379     puts  ("When URL is specified, pjsua will immediately initiate call to that URL");
00380     puts  ("");
00381 
00382     fflush(stdout);
00383 }
00384 
00385 
00386 /* Set default config. */
00387 static void default_config(struct app_config *cfg)
00388 {
00389     char tmp[80];
00390     unsigned i;
00391 
00392     pjsua_config_default(&cfg->cfg);
00393     pj_ansi_sprintf(tmp, "PJSUA v%s %s", pj_get_version(),
00394                     pj_get_sys_info()->info.ptr);
00395     pj_strdup2_with_null(app_config.pool, &cfg->cfg.user_agent, tmp);
00396 
00397     pjsua_logging_config_default(&cfg->log_cfg);
00398     pjsua_media_config_default(&cfg->media_cfg);
00399     pjsua_transport_config_default(&cfg->udp_cfg);
00400     cfg->udp_cfg.port = 5060;
00401     pjsua_transport_config_default(&cfg->rtp_cfg);
00402     cfg->rtp_cfg.port = 4000;
00403     cfg->redir_op = PJSIP_REDIRECT_ACCEPT;
00404     cfg->duration = NO_LIMIT;
00405     cfg->wav_id = PJSUA_INVALID_ID;
00406     cfg->rec_id = PJSUA_INVALID_ID;
00407     cfg->wav_port = PJSUA_INVALID_ID;
00408     cfg->rec_port = PJSUA_INVALID_ID;
00409     cfg->mic_level = cfg->speaker_level = 1.0;
00410     cfg->capture_dev = PJSUA_INVALID_ID;
00411     cfg->playback_dev = PJSUA_INVALID_ID;
00412     cfg->capture_lat = PJMEDIA_SND_DEFAULT_REC_LATENCY;
00413     cfg->playback_lat = PJMEDIA_SND_DEFAULT_PLAY_LATENCY;
00414     cfg->ringback_slot = PJSUA_INVALID_ID;
00415     cfg->ring_slot = PJSUA_INVALID_ID;
00416 
00417     for (i=0; i<PJ_ARRAY_SIZE(cfg->acc_cfg); ++i)
00418         pjsua_acc_config_default(&cfg->acc_cfg[i]);
00419 
00420     for (i=0; i<PJ_ARRAY_SIZE(cfg->buddy_cfg); ++i)
00421         pjsua_buddy_config_default(&cfg->buddy_cfg[i]);
00422 
00423     cfg->vid.vcapture_dev = PJMEDIA_VID_DEFAULT_CAPTURE_DEV;
00424     cfg->vid.vrender_dev = PJMEDIA_VID_DEFAULT_RENDER_DEV;
00425     cfg->aud_cnt = 1;
00426 
00427     cfg->avi_def_idx = PJSUA_INVALID_ID;
00428 }
00429 
00430 
00431 /*
00432  * Read command arguments from config file.
00433  */
00434 static int read_config_file(pj_pool_t *pool, const char *filename, 
00435                             int *app_argc, char ***app_argv)
00436 {
00437     int i;
00438     FILE *fhnd;
00439     char line[200];
00440     int argc = 0;
00441     char **argv;
00442     enum { MAX_ARGS = 128 };
00443 
00444     /* Allocate MAX_ARGS+1 (argv needs to be terminated with NULL argument) */
00445     argv = pj_pool_calloc(pool, MAX_ARGS+1, sizeof(char*));
00446     argv[argc++] = *app_argv[0];
00447 
00448     /* Open config file. */
00449     fhnd = fopen(filename, "rt");
00450     if (!fhnd) {
00451         PJ_LOG(1,(THIS_FILE, "Unable to open config file %s", filename));
00452         fflush(stdout);
00453         return -1;
00454     }
00455 
00456     /* Scan tokens in the file. */
00457     while (argc < MAX_ARGS && !feof(fhnd)) {
00458         char  *token;
00459         char  *p;
00460         const char *whitespace = " \t\r\n";
00461         char  cDelimiter;
00462         int   len, token_len;
00463         
00464         if (fgets(line, sizeof(line), fhnd) == NULL) break;
00465         
00466         // Trim ending newlines
00467         len = strlen(line);
00468         if (line[len-1]=='\n')
00469             line[--len] = '\0';
00470         if (line[len-1]=='\r')
00471             line[--len] = '\0';
00472 
00473         if (len==0) continue;
00474 
00475         for (p = line; *p != '\0' && argc < MAX_ARGS; p++) {
00476             // first, scan whitespaces
00477             while (*p != '\0' && strchr(whitespace, *p) != NULL) p++;
00478 
00479             if (*p == '\0')                 // are we done yet?
00480                 break;
00481             
00482             if (*p == '"' || *p == '\'') {    // is token a quoted string
00483                 cDelimiter = *p++;          // save quote delimiter
00484                 token = p;
00485                 
00486                 while (*p != '\0' && *p != cDelimiter) p++;
00487                 
00488                 if (*p == '\0')         // found end of the line, but,
00489                     cDelimiter = '\0';  // didn't find a matching quote
00490 
00491             } else {                    // token's not a quoted string
00492                 token = p;
00493                 
00494                 while (*p != '\0' && strchr(whitespace, *p) == NULL) p++;
00495                 
00496                 cDelimiter = *p;
00497             }
00498             
00499             *p = '\0';
00500             token_len = p-token;
00501             
00502             if (token_len > 0) {
00503                 if (*token == '#')
00504                     break;  // ignore remainder of line
00505                 
00506                 argv[argc] = pj_pool_alloc(pool, token_len + 1);
00507                 pj_memcpy(argv[argc], token, token_len + 1);
00508                 ++argc;
00509             }
00510             
00511             *p = cDelimiter;
00512         }
00513     }
00514 
00515     /* Copy arguments from command line */
00516     for (i=1; i<*app_argc && argc < MAX_ARGS; ++i)
00517         argv[argc++] = (*app_argv)[i];
00518 
00519     if (argc == MAX_ARGS && (i!=*app_argc || !feof(fhnd))) {
00520         PJ_LOG(1,(THIS_FILE, 
00521                   "Too many arguments specified in cmd line/config file"));
00522         fflush(stdout);
00523         fclose(fhnd);
00524         return -1;
00525     }
00526 
00527     fclose(fhnd);
00528 
00529     /* Assign the new command line back to the original command line. */
00530     *app_argc = argc;
00531     *app_argv = argv;
00532     return 0;
00533 
00534 }
00535 
00536 static int my_atoi(const char *cs)
00537 {
00538     pj_str_t s;
00539 
00540     pj_cstr(&s, cs);
00541     if (cs[0] == '-') {
00542         s.ptr++, s.slen--;
00543         return 0 - (int)pj_strtoul(&s);
00544     } else if (cs[0] == '+') {
00545         s.ptr++, s.slen--;
00546         return pj_strtoul(&s);
00547     } else {
00548         return pj_strtoul(&s);
00549     }
00550 }
00551 
00552 
00553 /* Parse arguments. */
00554 static pj_status_t parse_args(int argc, char *argv[],
00555                               struct app_config *cfg,
00556                               pj_str_t *uri_to_call)
00557 {
00558     int c;
00559     int option_index;
00560     enum { OPT_CONFIG_FILE=127, OPT_LOG_FILE, OPT_LOG_LEVEL, OPT_APP_LOG_LEVEL, 
00561            OPT_LOG_APPEND, OPT_COLOR, OPT_NO_COLOR, OPT_LIGHT_BG, OPT_NO_STDERR,
00562            OPT_HELP, OPT_VERSION, OPT_NULL_AUDIO, OPT_SND_AUTO_CLOSE,
00563            OPT_LOCAL_PORT, OPT_IP_ADDR, OPT_PROXY, OPT_OUTBOUND_PROXY, 
00564            OPT_REGISTRAR, OPT_REG_TIMEOUT, OPT_PUBLISH, OPT_ID, OPT_CONTACT,
00565            OPT_BOUND_ADDR, OPT_CONTACT_PARAMS, OPT_CONTACT_URI_PARAMS,
00566            OPT_100REL, OPT_USE_IMS, OPT_REALM, OPT_USERNAME, OPT_PASSWORD,
00567            OPT_REG_RETRY_INTERVAL, OPT_REG_USE_PROXY,
00568            OPT_MWI, OPT_NAMESERVER, OPT_STUN_SRV, OPT_OUTB_RID,
00569            OPT_ADD_BUDDY, OPT_OFFER_X_MS_MSG, OPT_NO_PRESENCE,
00570            OPT_AUTO_ANSWER, OPT_AUTO_PLAY, OPT_AUTO_PLAY_HANGUP, OPT_AUTO_LOOP,
00571            OPT_AUTO_CONF, OPT_CLOCK_RATE, OPT_SND_CLOCK_RATE, OPT_STEREO,
00572            OPT_USE_ICE, OPT_ICE_REGULAR, OPT_USE_SRTP, OPT_SRTP_SECURE,
00573            OPT_USE_TURN, OPT_ICE_MAX_HOSTS, OPT_ICE_NO_RTCP, OPT_TURN_SRV, 
00574            OPT_TURN_TCP, OPT_TURN_USER, OPT_TURN_PASSWD,
00575            OPT_PLAY_FILE, OPT_PLAY_TONE, OPT_RTP_PORT, OPT_ADD_CODEC, 
00576            OPT_ILBC_MODE, OPT_REC_FILE, OPT_AUTO_REC,
00577            OPT_COMPLEXITY, OPT_QUALITY, OPT_PTIME, OPT_NO_VAD,
00578            OPT_RX_DROP_PCT, OPT_TX_DROP_PCT, OPT_EC_TAIL, OPT_EC_OPT,
00579            OPT_NEXT_ACCOUNT, OPT_NEXT_CRED, OPT_MAX_CALLS, 
00580            OPT_DURATION, OPT_NO_TCP, OPT_NO_UDP, OPT_THREAD_CNT,
00581            OPT_NOREFERSUB, OPT_ACCEPT_REDIRECT,
00582            OPT_USE_TLS, OPT_TLS_CA_FILE, OPT_TLS_CERT_FILE, OPT_TLS_PRIV_FILE,
00583            OPT_TLS_PASSWORD, OPT_TLS_VERIFY_SERVER, OPT_TLS_VERIFY_CLIENT,
00584            OPT_TLS_NEG_TIMEOUT, OPT_TLS_CIPHER,
00585            OPT_CAPTURE_DEV, OPT_PLAYBACK_DEV,
00586            OPT_CAPTURE_LAT, OPT_PLAYBACK_LAT, OPT_NO_TONES, OPT_JB_MAX_SIZE,
00587            OPT_STDOUT_REFRESH, OPT_STDOUT_REFRESH_TEXT, OPT_IPV6, OPT_QOS,
00588 #ifdef _IONBF
00589            OPT_STDOUT_NO_BUF,
00590 #endif
00591            OPT_AUTO_UPDATE_NAT,OPT_USE_COMPACT_FORM,OPT_DIS_CODEC,
00592            OPT_NO_FORCE_LR,
00593            OPT_TIMER, OPT_TIMER_SE, OPT_TIMER_MIN_SE,
00594            OPT_VIDEO, OPT_EXTRA_AUDIO,
00595            OPT_VCAPTURE_DEV, OPT_VRENDER_DEV, OPT_PLAY_AVI, OPT_AUTO_PLAY_AVI
00596     };
00597     struct pj_getopt_option long_options[] = {
00598         { "config-file",1, 0, OPT_CONFIG_FILE},
00599         { "log-file",   1, 0, OPT_LOG_FILE},
00600         { "log-level",  1, 0, OPT_LOG_LEVEL},
00601         { "app-log-level",1,0,OPT_APP_LOG_LEVEL},
00602         { "log-append", 0, 0, OPT_LOG_APPEND},
00603         { "color",      0, 0, OPT_COLOR},
00604         { "no-color",   0, 0, OPT_NO_COLOR},
00605         { "light-bg",           0, 0, OPT_LIGHT_BG},
00606         { "no-stderr",  0, 0, OPT_NO_STDERR},
00607         { "help",       0, 0, OPT_HELP},
00608         { "version",    0, 0, OPT_VERSION},
00609         { "clock-rate", 1, 0, OPT_CLOCK_RATE},
00610         { "snd-clock-rate",     1, 0, OPT_SND_CLOCK_RATE},
00611         { "stereo",     0, 0, OPT_STEREO},
00612         { "null-audio", 0, 0, OPT_NULL_AUDIO},
00613         { "local-port", 1, 0, OPT_LOCAL_PORT},
00614         { "ip-addr",    1, 0, OPT_IP_ADDR},
00615         { "bound-addr", 1, 0, OPT_BOUND_ADDR},
00616         { "no-tcp",     0, 0, OPT_NO_TCP},
00617         { "no-udp",     0, 0, OPT_NO_UDP},
00618         { "norefersub", 0, 0, OPT_NOREFERSUB},
00619         { "proxy",      1, 0, OPT_PROXY},
00620         { "outbound",   1, 0, OPT_OUTBOUND_PROXY},
00621         { "registrar",  1, 0, OPT_REGISTRAR},
00622         { "reg-timeout",1, 0, OPT_REG_TIMEOUT},
00623         { "publish",    0, 0, OPT_PUBLISH},
00624         { "mwi",        0, 0, OPT_MWI},
00625         { "use-100rel", 0, 0, OPT_100REL},
00626         { "use-ims",    0, 0, OPT_USE_IMS},
00627         { "id",         1, 0, OPT_ID},
00628         { "contact",    1, 0, OPT_CONTACT},
00629         { "contact-params",1,0, OPT_CONTACT_PARAMS},
00630         { "contact-uri-params",1,0, OPT_CONTACT_URI_PARAMS},
00631         { "auto-update-nat",    1, 0, OPT_AUTO_UPDATE_NAT},
00632         { "use-compact-form",   0, 0, OPT_USE_COMPACT_FORM},
00633         { "accept-redirect", 1, 0, OPT_ACCEPT_REDIRECT},
00634         { "no-force-lr",0, 0, OPT_NO_FORCE_LR},
00635         { "realm",      1, 0, OPT_REALM},
00636         { "username",   1, 0, OPT_USERNAME},
00637         { "password",   1, 0, OPT_PASSWORD},
00638         { "rereg-delay",1, 0, OPT_REG_RETRY_INTERVAL},
00639         { "reg-use-proxy", 1, 0, OPT_REG_USE_PROXY},
00640         { "nameserver", 1, 0, OPT_NAMESERVER},
00641         { "stun-srv",   1, 0, OPT_STUN_SRV},
00642         { "add-buddy",  1, 0, OPT_ADD_BUDDY},
00643         { "offer-x-ms-msg",0,0,OPT_OFFER_X_MS_MSG},
00644         { "no-presence", 0, 0, OPT_NO_PRESENCE},
00645         { "auto-answer",1, 0, OPT_AUTO_ANSWER},
00646         { "auto-play",  0, 0, OPT_AUTO_PLAY},
00647         { "auto-play-hangup",0, 0, OPT_AUTO_PLAY_HANGUP},
00648         { "auto-rec",   0, 0, OPT_AUTO_REC},
00649         { "auto-loop",  0, 0, OPT_AUTO_LOOP},
00650         { "auto-conf",  0, 0, OPT_AUTO_CONF},
00651         { "play-file",  1, 0, OPT_PLAY_FILE},
00652         { "play-tone",  1, 0, OPT_PLAY_TONE},
00653         { "rec-file",   1, 0, OPT_REC_FILE},
00654         { "rtp-port",   1, 0, OPT_RTP_PORT},
00655 
00656         { "use-ice",    0, 0, OPT_USE_ICE},
00657         { "ice-regular",0, 0, OPT_ICE_REGULAR},
00658         { "use-turn",   0, 0, OPT_USE_TURN},
00659         { "ice-max-hosts",1, 0, OPT_ICE_MAX_HOSTS},
00660         { "ice-no-rtcp",0, 0, OPT_ICE_NO_RTCP},
00661         { "turn-srv",   1, 0, OPT_TURN_SRV},
00662         { "turn-tcp",   0, 0, OPT_TURN_TCP},
00663         { "turn-user",  1, 0, OPT_TURN_USER},
00664         { "turn-passwd",1, 0, OPT_TURN_PASSWD},
00665 
00666 #if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
00667         { "use-srtp",   1, 0, OPT_USE_SRTP},
00668         { "srtp-secure",1, 0, OPT_SRTP_SECURE},
00669 #endif
00670         { "add-codec",  1, 0, OPT_ADD_CODEC},
00671         { "dis-codec",  1, 0, OPT_DIS_CODEC},
00672         { "complexity", 1, 0, OPT_COMPLEXITY},
00673         { "quality",    1, 0, OPT_QUALITY},
00674         { "ptime",      1, 0, OPT_PTIME},
00675         { "no-vad",     0, 0, OPT_NO_VAD},
00676         { "ec-tail",    1, 0, OPT_EC_TAIL},
00677         { "ec-opt",     1, 0, OPT_EC_OPT},
00678         { "ilbc-mode",  1, 0, OPT_ILBC_MODE},
00679         { "rx-drop-pct",1, 0, OPT_RX_DROP_PCT},
00680         { "tx-drop-pct",1, 0, OPT_TX_DROP_PCT},
00681         { "next-account",0,0, OPT_NEXT_ACCOUNT},
00682         { "next-cred",  0, 0, OPT_NEXT_CRED},
00683         { "max-calls",  1, 0, OPT_MAX_CALLS},
00684         { "duration",   1, 0, OPT_DURATION},
00685         { "thread-cnt", 1, 0, OPT_THREAD_CNT},
00686 #if defined(PJSIP_HAS_TLS_TRANSPORT) && (PJSIP_HAS_TLS_TRANSPORT != 0)
00687         { "use-tls",    0, 0, OPT_USE_TLS}, 
00688         { "tls-ca-file",1, 0, OPT_TLS_CA_FILE},
00689         { "tls-cert-file",1,0, OPT_TLS_CERT_FILE}, 
00690         { "tls-privkey-file",1,0, OPT_TLS_PRIV_FILE},
00691         { "tls-password",1,0, OPT_TLS_PASSWORD},
00692         { "tls-verify-server", 0, 0, OPT_TLS_VERIFY_SERVER},
00693         { "tls-verify-client", 0, 0, OPT_TLS_VERIFY_CLIENT},
00694         { "tls-neg-timeout", 1, 0, OPT_TLS_NEG_TIMEOUT},
00695         { "tls-cipher", 1, 0, OPT_TLS_CIPHER},
00696 #endif
00697         { "capture-dev",    1, 0, OPT_CAPTURE_DEV},
00698         { "playback-dev",   1, 0, OPT_PLAYBACK_DEV},
00699         { "capture-lat",    1, 0, OPT_CAPTURE_LAT},
00700         { "playback-lat",   1, 0, OPT_PLAYBACK_LAT},
00701         { "stdout-refresh", 1, 0, OPT_STDOUT_REFRESH},
00702         { "stdout-refresh-text", 1, 0, OPT_STDOUT_REFRESH_TEXT},
00703 #ifdef _IONBF
00704         { "stdout-no-buf",  0, 0, OPT_STDOUT_NO_BUF },
00705 #endif
00706         { "snd-auto-close", 1, 0, OPT_SND_AUTO_CLOSE},
00707         { "no-tones",    0, 0, OPT_NO_TONES},
00708         { "jb-max-size", 1, 0, OPT_JB_MAX_SIZE},
00709 #if defined(PJ_HAS_IPV6) && PJ_HAS_IPV6
00710         { "ipv6",        0, 0, OPT_IPV6},
00711 #endif
00712         { "set-qos",     0, 0, OPT_QOS},
00713         { "use-timer",  1, 0, OPT_TIMER},
00714         { "timer-se",   1, 0, OPT_TIMER_SE},
00715         { "timer-min-se", 1, 0, OPT_TIMER_MIN_SE},
00716         { "outb-rid",   1, 0, OPT_OUTB_RID},
00717         { "video",      0, 0, OPT_VIDEO},
00718         { "extra-audio",0, 0, OPT_EXTRA_AUDIO},
00719         { "vcapture-dev", 1, 0, OPT_VCAPTURE_DEV},
00720         { "vrender-dev",  1, 0, OPT_VRENDER_DEV},
00721         { "play-avi",   1, 0, OPT_PLAY_AVI},
00722         { "auto-play-avi", 0, 0, OPT_AUTO_PLAY_AVI},
00723         { NULL, 0, 0, 0}
00724     };
00725     pj_status_t status;
00726     pjsua_acc_config *cur_acc;
00727     char *config_file = NULL;
00728     unsigned i;
00729 
00730     /* Run pj_getopt once to see if user specifies config file to read. */ 
00731     pj_optind = 0;
00732     while ((c=pj_getopt_long(argc, argv, "", long_options, 
00733                              &option_index)) != -1) 
00734     {
00735         switch (c) {
00736         case OPT_CONFIG_FILE:
00737             config_file = pj_optarg;
00738             break;
00739         }
00740         if (config_file)
00741             break;
00742     }
00743 
00744     if (config_file) {
00745         status = read_config_file(app_config.pool, config_file, &argc, &argv);
00746         if (status != 0)
00747             return status;
00748     }
00749 
00750     cfg->acc_cnt = 0;
00751     cur_acc = &cfg->acc_cfg[0];
00752 
00753 
00754     /* Reinitialize and re-run pj_getopt again, possibly with new arguments
00755      * read from config file.
00756      */
00757     pj_optind = 0;
00758     while((c=pj_getopt_long(argc,argv, "", long_options,&option_index))!=-1) {
00759         pj_str_t tmp;
00760         long lval;
00761 
00762         switch (c) {
00763 
00764         case OPT_CONFIG_FILE:
00765             /* Ignore as this has been processed before */
00766             break;
00767         
00768         case OPT_LOG_FILE:
00769             cfg->log_cfg.log_filename = pj_str(pj_optarg);
00770             break;
00771 
00772         case OPT_LOG_LEVEL:
00773             c = pj_strtoul(pj_cstr(&tmp, pj_optarg));
00774             if (c < 0 || c > 6) {
00775                 PJ_LOG(1,(THIS_FILE, 
00776                           "Error: expecting integer value 0-6 "
00777                           "for --log-level"));
00778                 return PJ_EINVAL;
00779             }
00780             cfg->log_cfg.level = c;
00781             pj_log_set_level( c );
00782             break;
00783 
00784         case OPT_APP_LOG_LEVEL:
00785             cfg->log_cfg.console_level = pj_strtoul(pj_cstr(&tmp, pj_optarg));
00786             if (cfg->log_cfg.console_level < 0 || cfg->log_cfg.console_level > 6) {
00787                 PJ_LOG(1,(THIS_FILE, 
00788                           "Error: expecting integer value 0-6 "
00789                           "for --app-log-level"));
00790                 return PJ_EINVAL;
00791             }
00792             break;
00793 
00794         case OPT_LOG_APPEND:
00795             cfg->log_cfg.log_file_flags |= PJ_O_APPEND;
00796             break;
00797 
00798         case OPT_COLOR:
00799             cfg->log_cfg.decor |= PJ_LOG_HAS_COLOR;
00800             break;
00801 
00802         case OPT_NO_COLOR:
00803             cfg->log_cfg.decor &= ~PJ_LOG_HAS_COLOR;
00804             break;
00805 
00806         case OPT_LIGHT_BG:
00807             pj_log_set_color(1, PJ_TERM_COLOR_R);
00808             pj_log_set_color(2, PJ_TERM_COLOR_R | PJ_TERM_COLOR_G);
00809             pj_log_set_color(3, PJ_TERM_COLOR_B | PJ_TERM_COLOR_G);
00810             pj_log_set_color(4, 0);
00811             pj_log_set_color(5, 0);
00812             pj_log_set_color(77, 0);
00813             break;
00814 
00815         case OPT_NO_STDERR:
00816             freopen("/dev/null", "w", stderr);
00817             break;
00818 
00819         case OPT_HELP:
00820             usage();
00821             return PJ_EINVAL;
00822 
00823         case OPT_VERSION:   /* version */
00824             pj_dump_config();
00825             return PJ_EINVAL;
00826 
00827         case OPT_NULL_AUDIO:
00828             cfg->null_audio = PJ_TRUE;
00829             break;
00830 
00831         case OPT_CLOCK_RATE:
00832             lval = pj_strtoul(pj_cstr(&tmp, pj_optarg));
00833             if (lval < 8000 || lval > 192000) {
00834                 PJ_LOG(1,(THIS_FILE, "Error: expecting value between "
00835                                      "8000-192000 for conference clock rate"));
00836                 return PJ_EINVAL;
00837             }
00838             cfg->media_cfg.clock_rate = lval; 
00839             break;
00840 
00841         case OPT_SND_CLOCK_RATE:
00842             lval = pj_strtoul(pj_cstr(&tmp, pj_optarg));
00843             if (lval < 8000 || lval > 192000) {
00844                 PJ_LOG(1,(THIS_FILE, "Error: expecting value between "
00845                                      "8000-192000 for sound device clock rate"));
00846                 return PJ_EINVAL;
00847             }
00848             cfg->media_cfg.snd_clock_rate = lval; 
00849             break;
00850 
00851         case OPT_STEREO:
00852             cfg->media_cfg.channel_count = 2;
00853             break;
00854 
00855         case OPT_LOCAL_PORT:   /* local-port */
00856             lval = pj_strtoul(pj_cstr(&tmp, pj_optarg));
00857             if (lval < 0 || lval > 65535) {
00858                 PJ_LOG(1,(THIS_FILE, 
00859                           "Error: expecting integer value for "
00860                           "--local-port"));
00861                 return PJ_EINVAL;
00862             }
00863             cfg->udp_cfg.port = (pj_uint16_t)lval;
00864             break;
00865 
00866         case OPT_IP_ADDR: /* ip-addr */
00867             cfg->udp_cfg.public_addr = pj_str(pj_optarg);
00868             cfg->rtp_cfg.public_addr = pj_str(pj_optarg);
00869             break;
00870 
00871         case OPT_BOUND_ADDR: /* bound-addr */
00872             cfg->udp_cfg.bound_addr = pj_str(pj_optarg);
00873             cfg->rtp_cfg.bound_addr = pj_str(pj_optarg);
00874             break;
00875 
00876         case OPT_NO_UDP: /* no-udp */
00877             if (cfg->no_tcp) {
00878               PJ_LOG(1,(THIS_FILE,"Error: can not disable both TCP and UDP"));
00879               return PJ_EINVAL;
00880             }
00881 
00882             cfg->no_udp = PJ_TRUE;
00883             break;
00884 
00885         case OPT_NOREFERSUB: /* norefersub */
00886             cfg->no_refersub = PJ_TRUE;
00887             break;
00888 
00889         case OPT_NO_TCP: /* no-tcp */
00890             if (cfg->no_udp) {
00891               PJ_LOG(1,(THIS_FILE,"Error: can not disable both TCP and UDP"));
00892               return PJ_EINVAL;
00893             }
00894 
00895             cfg->no_tcp = PJ_TRUE;
00896             break;
00897 
00898         case OPT_PROXY:   /* proxy */
00899             if (pjsua_verify_sip_url(pj_optarg) != 0) {
00900                 PJ_LOG(1,(THIS_FILE, 
00901                           "Error: invalid SIP URL '%s' "
00902                           "in proxy argument", pj_optarg));
00903                 return PJ_EINVAL;
00904             }
00905             cur_acc->proxy[cur_acc->proxy_cnt++] = pj_str(pj_optarg);
00906             break;
00907 
00908         case OPT_OUTBOUND_PROXY:   /* outbound proxy */
00909             if (pjsua_verify_sip_url(pj_optarg) != 0) {
00910                 PJ_LOG(1,(THIS_FILE, 
00911                           "Error: invalid SIP URL '%s' "
00912                           "in outbound proxy argument", pj_optarg));
00913                 return PJ_EINVAL;
00914             }
00915             cfg->cfg.outbound_proxy[cfg->cfg.outbound_proxy_cnt++] = pj_str(pj_optarg);
00916             break;
00917 
00918         case OPT_REGISTRAR:   /* registrar */
00919             if (pjsua_verify_sip_url(pj_optarg) != 0) {
00920                 PJ_LOG(1,(THIS_FILE, 
00921                           "Error: invalid SIP URL '%s' in "
00922                           "registrar argument", pj_optarg));
00923                 return PJ_EINVAL;
00924             }
00925             cur_acc->reg_uri = pj_str(pj_optarg);
00926             break;
00927 
00928         case OPT_REG_TIMEOUT:   /* reg-timeout */
00929             cur_acc->reg_timeout = pj_strtoul(pj_cstr(&tmp,pj_optarg));
00930             if (cur_acc->reg_timeout < 1 || cur_acc->reg_timeout > 3600) {
00931                 PJ_LOG(1,(THIS_FILE, 
00932                           "Error: invalid value for --reg-timeout "
00933                           "(expecting 1-3600)"));
00934                 return PJ_EINVAL;
00935             }
00936             break;
00937 
00938         case OPT_PUBLISH:   /* publish */
00939             cur_acc->publish_enabled = PJ_TRUE;
00940             break;
00941 
00942         case OPT_MWI:   /* mwi */
00943             cur_acc->mwi_enabled = PJ_TRUE;
00944             break;
00945 
00946         case OPT_100REL: 
00947             cur_acc->require_100rel = PJSUA_100REL_MANDATORY;
00948             cfg->cfg.require_100rel = PJSUA_100REL_MANDATORY;
00949             break;
00950 
00951         case OPT_TIMER: 
00952             lval = pj_strtoul(pj_cstr(&tmp, pj_optarg));
00953             if (lval < 0 || lval > 3) {
00954                 PJ_LOG(1,(THIS_FILE, 
00955                           "Error: expecting integer value 0-3 for --use-timer"));
00956                 return PJ_EINVAL;
00957             }
00958             cur_acc->use_timer = lval;
00959             cfg->cfg.use_timer = lval;
00960             break;
00961 
00962         case OPT_TIMER_SE: 
00963             cur_acc->timer_setting.sess_expires = pj_strtoul(pj_cstr(&tmp, pj_optarg));
00964             if (cur_acc->timer_setting.sess_expires < 90) {
00965                 PJ_LOG(1,(THIS_FILE, 
00966                           "Error: invalid value for --timer-se "
00967                           "(expecting higher than 90)"));
00968                 return PJ_EINVAL;
00969             }
00970             cfg->cfg.timer_setting.sess_expires = cur_acc->timer_setting.sess_expires;
00971             break;
00972 
00973         case OPT_TIMER_MIN_SE: 
00974             cur_acc->timer_setting.min_se = pj_strtoul(pj_cstr(&tmp, pj_optarg));
00975             if (cur_acc->timer_setting.min_se < 90) {
00976                 PJ_LOG(1,(THIS_FILE, 
00977                           "Error: invalid value for --timer-min-se "
00978                           "(expecting higher than 90)"));
00979                 return PJ_EINVAL;
00980             }
00981             cfg->cfg.timer_setting.min_se = cur_acc->timer_setting.min_se;
00982             break;
00983 
00984         case OPT_OUTB_RID: /* Outbound reg-id */
00985             cur_acc->rfc5626_reg_id = pj_str(pj_optarg);
00986             break;
00987 
00988         case OPT_USE_IMS: /* Activate IMS settings */
00989             cur_acc->auth_pref.initial_auth = PJ_TRUE;
00990             break;
00991 
00992         case OPT_ID:   /* id */
00993             if (pjsua_verify_url(pj_optarg) != 0) {
00994                 PJ_LOG(1,(THIS_FILE, 
00995                           "Error: invalid SIP URL '%s' "
00996                           "in local id argument", pj_optarg));
00997                 return PJ_EINVAL;
00998             }
00999             cur_acc->id = pj_str(pj_optarg);
01000             break;
01001 
01002         case OPT_CONTACT:   /* contact */
01003             if (pjsua_verify_sip_url(pj_optarg) != 0) {
01004                 PJ_LOG(1,(THIS_FILE, 
01005                           "Error: invalid SIP URL '%s' "
01006                           "in contact argument", pj_optarg));
01007                 return PJ_EINVAL;
01008             }
01009             cur_acc->force_contact = pj_str(pj_optarg);
01010             break;
01011 
01012         case OPT_CONTACT_PARAMS:
01013             cur_acc->contact_params = pj_str(pj_optarg);
01014             break;
01015 
01016         case OPT_CONTACT_URI_PARAMS:
01017             cur_acc->contact_uri_params = pj_str(pj_optarg);
01018             break;
01019 
01020         case OPT_AUTO_UPDATE_NAT:   /* OPT_AUTO_UPDATE_NAT */
01021             cur_acc->allow_contact_rewrite  = pj_strtoul(pj_cstr(&tmp, pj_optarg));
01022             break;
01023 
01024         case OPT_USE_COMPACT_FORM:
01025             /* enable compact form - from Ticket #342 */
01026             {
01027                 extern pj_bool_t pjsip_use_compact_form;
01028                 extern pj_bool_t pjsip_include_allow_hdr_in_dlg;
01029                 extern pj_bool_t pjmedia_add_rtpmap_for_static_pt;
01030 
01031                 pjsip_use_compact_form = PJ_TRUE;
01032                 /* do not transmit Allow header */
01033                 pjsip_include_allow_hdr_in_dlg = PJ_FALSE;
01034                 /* Do not include rtpmap for static payload types (<96) */
01035                 pjmedia_add_rtpmap_for_static_pt = PJ_FALSE;
01036             }
01037             break;
01038 
01039         case OPT_ACCEPT_REDIRECT:
01040             cfg->redir_op = my_atoi(pj_optarg);
01041             if (cfg->redir_op<0 || cfg->redir_op>PJSIP_REDIRECT_STOP) {
01042                 PJ_LOG(1,(THIS_FILE, 
01043                           "Error: accept-redirect value '%s' ", pj_optarg));
01044                 return PJ_EINVAL;
01045             }
01046             break;
01047 
01048         case OPT_NO_FORCE_LR:
01049             cfg->cfg.force_lr = PJ_FALSE;
01050             break;
01051 
01052         case OPT_NEXT_ACCOUNT: /* Add more account. */
01053             cfg->acc_cnt++;
01054             cur_acc = &cfg->acc_cfg[cfg->acc_cnt];
01055             break;
01056 
01057         case OPT_USERNAME:   /* Default authentication user */
01058             cur_acc->cred_info[cur_acc->cred_count].username = pj_str(pj_optarg);
01059             cur_acc->cred_info[cur_acc->cred_count].scheme = pj_str("Digest");
01060             break;
01061 
01062         case OPT_REALM:     /* Default authentication realm. */
01063             cur_acc->cred_info[cur_acc->cred_count].realm = pj_str(pj_optarg);
01064             break;
01065 
01066         case OPT_PASSWORD:   /* authentication password */
01067             cur_acc->cred_info[cur_acc->cred_count].data_type = PJSIP_CRED_DATA_PLAIN_PASSWD;
01068             cur_acc->cred_info[cur_acc->cred_count].data = pj_str(pj_optarg);
01069 #if PJSIP_HAS_DIGEST_AKA_AUTH
01070             cur_acc->cred_info[cur_acc->cred_count].data_type |= PJSIP_CRED_DATA_EXT_AKA;
01071             cur_acc->cred_info[cur_acc->cred_count].ext.aka.k = pj_str(pj_optarg);
01072             cur_acc->cred_info[cur_acc->cred_count].ext.aka.cb = &pjsip_auth_create_aka_response;
01073 #endif
01074             break;
01075 
01076         case OPT_REG_RETRY_INTERVAL:
01077             cur_acc->reg_retry_interval = pj_strtoul(pj_cstr(&tmp, pj_optarg));
01078             break;
01079 
01080         case OPT_REG_USE_PROXY:
01081             cur_acc->reg_use_proxy = (unsigned)pj_strtoul(pj_cstr(&tmp, pj_optarg));
01082             if (cur_acc->reg_use_proxy > 3) {
01083                 PJ_LOG(1,(THIS_FILE, "Error: invalid --reg-use-proxy value '%s'",
01084                           pj_optarg));
01085                 return PJ_EINVAL;
01086             }
01087             break;
01088 
01089         case OPT_NEXT_CRED: /* next credential */
01090             cur_acc->cred_count++;
01091             break;
01092 
01093         case OPT_NAMESERVER: /* nameserver */
01094             cfg->cfg.nameserver[cfg->cfg.nameserver_count++] = pj_str(pj_optarg);
01095             if (cfg->cfg.nameserver_count > PJ_ARRAY_SIZE(cfg->cfg.nameserver)) {
01096                 PJ_LOG(1,(THIS_FILE, "Error: too many nameservers"));
01097                 return PJ_ETOOMANY;
01098             }
01099             break;
01100 
01101         case OPT_STUN_SRV:   /* STUN server */
01102             cfg->cfg.stun_host = pj_str(pj_optarg);
01103             if (cfg->cfg.stun_srv_cnt==PJ_ARRAY_SIZE(cfg->cfg.stun_srv)) {
01104                 PJ_LOG(1,(THIS_FILE, "Error: too many STUN servers"));
01105                 return PJ_ETOOMANY;
01106             }
01107             cfg->cfg.stun_srv[cfg->cfg.stun_srv_cnt++] = pj_str(pj_optarg);
01108             break;
01109 
01110         case OPT_ADD_BUDDY: /* Add to buddy list. */
01111             if (pjsua_verify_url(pj_optarg) != 0) {
01112                 PJ_LOG(1,(THIS_FILE, 
01113                           "Error: invalid URL '%s' in "
01114                           "--add-buddy option", pj_optarg));
01115                 return -1;
01116             }
01117             if (cfg->buddy_cnt == PJ_ARRAY_SIZE(cfg->buddy_cfg)) {
01118                 PJ_LOG(1,(THIS_FILE, 
01119                           "Error: too many buddies in buddy list."));
01120                 return -1;
01121             }
01122             cfg->buddy_cfg[cfg->buddy_cnt].uri = pj_str(pj_optarg);
01123             cfg->buddy_cnt++;
01124             break;
01125 
01126         case OPT_AUTO_PLAY:
01127             cfg->auto_play = 1;
01128             break;
01129 
01130         case OPT_AUTO_PLAY_HANGUP:
01131             cfg->auto_play_hangup = 1;
01132             break;
01133 
01134         case OPT_AUTO_REC:
01135             cfg->auto_rec = 1;
01136             break;
01137 
01138         case OPT_AUTO_LOOP:
01139             cfg->auto_loop = 1;
01140             break;
01141 
01142         case OPT_AUTO_CONF:
01143             cfg->auto_conf = 1;
01144             break;
01145 
01146         case OPT_PLAY_FILE:
01147             cfg->wav_files[cfg->wav_count++] = pj_str(pj_optarg);
01148             break;
01149 
01150         case OPT_PLAY_TONE:
01151             {
01152                 int f1, f2, on, off;
01153                 int n;
01154 
01155                 n = sscanf(pj_optarg, "%d,%d,%d,%d", &f1, &f2, &on, &off);
01156                 if (n != 4) {
01157                     puts("Expecting f1,f2,on,off in --play-tone");
01158                     return -1;
01159                 }
01160 
01161                 cfg->tones[cfg->tone_count].freq1 = (short)f1;
01162                 cfg->tones[cfg->tone_count].freq2 = (short)f2;
01163                 cfg->tones[cfg->tone_count].on_msec = (short)on;
01164                 cfg->tones[cfg->tone_count].off_msec = (short)off;
01165                 ++cfg->tone_count;
01166             }
01167             break;
01168 
01169         case OPT_REC_FILE:
01170             cfg->rec_file = pj_str(pj_optarg);
01171             break;
01172 
01173         case OPT_USE_ICE:
01174             cfg->media_cfg.enable_ice = PJ_TRUE;
01175             break;
01176 
01177         case OPT_ICE_REGULAR:
01178             cfg->media_cfg.ice_opt.aggressive = PJ_FALSE;
01179             break;
01180 
01181         case OPT_USE_TURN:
01182             cfg->media_cfg.enable_turn = PJ_TRUE;
01183             break;
01184 
01185         case OPT_ICE_MAX_HOSTS:
01186             cfg->media_cfg.ice_max_host_cands = my_atoi(pj_optarg);
01187             break;
01188 
01189         case OPT_ICE_NO_RTCP:
01190             cfg->media_cfg.ice_no_rtcp = PJ_TRUE;
01191             break;
01192 
01193         case OPT_TURN_SRV:
01194             cfg->media_cfg.turn_server = pj_str(pj_optarg);
01195             break;
01196 
01197         case OPT_TURN_TCP:
01198             cfg->media_cfg.turn_conn_type = PJ_TURN_TP_TCP;
01199             break;
01200 
01201         case OPT_TURN_USER:
01202             cfg->media_cfg.turn_auth_cred.type = PJ_STUN_AUTH_CRED_STATIC;
01203             cfg->media_cfg.turn_auth_cred.data.static_cred.realm = pj_str("*");
01204             cfg->media_cfg.turn_auth_cred.data.static_cred.username = pj_str(pj_optarg);
01205             break;
01206 
01207         case OPT_TURN_PASSWD:
01208             cfg->media_cfg.turn_auth_cred.data.static_cred.data_type = PJ_STUN_PASSWD_PLAIN;
01209             cfg->media_cfg.turn_auth_cred.data.static_cred.data = pj_str(pj_optarg);
01210             break;
01211 
01212 #if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
01213         case OPT_USE_SRTP:
01214             app_config.cfg.use_srtp = my_atoi(pj_optarg);
01215             if (!pj_isdigit(*pj_optarg) || app_config.cfg.use_srtp > 3) {
01216                 PJ_LOG(1,(THIS_FILE, "Invalid value for --use-srtp option"));
01217                 return -1;
01218             }
01219             if ((int)app_config.cfg.use_srtp == 3) {
01220                 /* SRTP optional mode with duplicated media offer */
01221                 app_config.cfg.use_srtp = PJMEDIA_SRTP_OPTIONAL;
01222                 app_config.cfg.srtp_optional_dup_offer = PJ_TRUE;
01223                 cur_acc->srtp_optional_dup_offer = PJ_TRUE;
01224             }
01225             cur_acc->use_srtp = app_config.cfg.use_srtp;
01226             break;
01227         case OPT_SRTP_SECURE:
01228             app_config.cfg.srtp_secure_signaling = my_atoi(pj_optarg);
01229             if (!pj_isdigit(*pj_optarg) || 
01230                 app_config.cfg.srtp_secure_signaling > 2) 
01231             {
01232                 PJ_LOG(1,(THIS_FILE, "Invalid value for --srtp-secure option"));
01233                 return -1;
01234             }
01235             cur_acc->srtp_secure_signaling = app_config.cfg.srtp_secure_signaling;
01236             break;
01237 #endif
01238 
01239         case OPT_RTP_PORT:
01240             cfg->rtp_cfg.port = my_atoi(pj_optarg);
01241             if (cfg->rtp_cfg.port == 0) {
01242                 enum { START_PORT=4000 };
01243                 unsigned range;
01244 
01245                 range = (65535-START_PORT-PJSUA_MAX_CALLS*2);
01246                 cfg->rtp_cfg.port = START_PORT + 
01247                                     ((pj_rand() % range) & 0xFFFE);
01248             }
01249 
01250             if (cfg->rtp_cfg.port < 1 || cfg->rtp_cfg.port > 65535) {
01251                 PJ_LOG(1,(THIS_FILE,
01252                           "Error: rtp-port argument value "
01253                           "(expecting 1-65535"));
01254                 return -1;
01255             }
01256             break;
01257 
01258         case OPT_DIS_CODEC:
01259             cfg->codec_dis[cfg->codec_dis_cnt++] = pj_str(pj_optarg);
01260             break;
01261 
01262         case OPT_ADD_CODEC:
01263             cfg->codec_arg[cfg->codec_cnt++] = pj_str(pj_optarg);
01264             break;
01265 
01266         /* These options were no longer valid after new pjsua */
01267         /*
01268         case OPT_COMPLEXITY:
01269             cfg->complexity = my_atoi(pj_optarg);
01270             if (cfg->complexity < 0 || cfg->complexity > 10) {
01271                 PJ_LOG(1,(THIS_FILE,
01272                           "Error: invalid --complexity (expecting 0-10"));
01273                 return -1;
01274             }
01275             break;
01276         */
01277 
01278         case OPT_DURATION:
01279             cfg->duration = my_atoi(pj_optarg);
01280             break;
01281 
01282         case OPT_THREAD_CNT:
01283             cfg->cfg.thread_cnt = my_atoi(pj_optarg);
01284             if (cfg->cfg.thread_cnt > 128) {
01285                 PJ_LOG(1,(THIS_FILE,
01286                           "Error: invalid --thread-cnt option"));
01287                 return -1;
01288             }
01289             break;
01290 
01291         case OPT_PTIME:
01292             cfg->media_cfg.ptime = my_atoi(pj_optarg);
01293             if (cfg->media_cfg.ptime < 10 || cfg->media_cfg.ptime > 1000) {
01294                 PJ_LOG(1,(THIS_FILE,
01295                           "Error: invalid --ptime option"));
01296                 return -1;
01297             }
01298             break;
01299 
01300         case OPT_NO_VAD:
01301             cfg->media_cfg.no_vad = PJ_TRUE;
01302             break;
01303 
01304         case OPT_EC_TAIL:
01305             cfg->media_cfg.ec_tail_len = my_atoi(pj_optarg);
01306             if (cfg->media_cfg.ec_tail_len > 1000) {
01307                 PJ_LOG(1,(THIS_FILE, "I think the ec-tail length setting "
01308                           "is too big"));
01309                 return -1;
01310             }
01311             break;
01312 
01313         case OPT_EC_OPT:
01314             cfg->media_cfg.ec_options = my_atoi(pj_optarg);
01315             break;
01316 
01317         case OPT_QUALITY:
01318             cfg->media_cfg.quality = my_atoi(pj_optarg);
01319             if (cfg->media_cfg.quality < 0 || cfg->media_cfg.quality > 10) {
01320                 PJ_LOG(1,(THIS_FILE,
01321                           "Error: invalid --quality (expecting 0-10"));
01322                 return -1;
01323             }
01324             break;
01325 
01326         case OPT_ILBC_MODE:
01327             cfg->media_cfg.ilbc_mode = my_atoi(pj_optarg);
01328             if (cfg->media_cfg.ilbc_mode!=20 && cfg->media_cfg.ilbc_mode!=30) {
01329                 PJ_LOG(1,(THIS_FILE,
01330                           "Error: invalid --ilbc-mode (expecting 20 or 30"));
01331                 return -1;
01332             }
01333             break;
01334 
01335         case OPT_RX_DROP_PCT:
01336             cfg->media_cfg.rx_drop_pct = my_atoi(pj_optarg);
01337             if (cfg->media_cfg.rx_drop_pct > 100) {
01338                 PJ_LOG(1,(THIS_FILE,
01339                           "Error: invalid --rx-drop-pct (expecting <= 100"));
01340                 return -1;
01341             }
01342             break;
01343             
01344         case OPT_TX_DROP_PCT:
01345             cfg->media_cfg.tx_drop_pct = my_atoi(pj_optarg);
01346             if (cfg->media_cfg.tx_drop_pct > 100) {
01347                 PJ_LOG(1,(THIS_FILE,
01348                           "Error: invalid --tx-drop-pct (expecting <= 100"));
01349                 return -1;
01350             }
01351             break;
01352 
01353         case OPT_AUTO_ANSWER:
01354             cfg->auto_answer = my_atoi(pj_optarg);
01355             if (cfg->auto_answer < 100 || cfg->auto_answer > 699) {
01356                 PJ_LOG(1,(THIS_FILE,
01357                           "Error: invalid code in --auto-answer "
01358                           "(expecting 100-699"));
01359                 return -1;
01360             }
01361             break;
01362 
01363         case OPT_MAX_CALLS:
01364             cfg->cfg.max_calls = my_atoi(pj_optarg);
01365             if (cfg->cfg.max_calls < 1 || cfg->cfg.max_calls > PJSUA_MAX_CALLS) {
01366                 PJ_LOG(1,(THIS_FILE,"Error: maximum call setting exceeds "
01367                                     "compile time limit (PJSUA_MAX_CALLS=%d)",
01368                           PJSUA_MAX_CALLS));
01369                 return -1;
01370             }
01371             break;
01372 
01373 #if defined(PJSIP_HAS_TLS_TRANSPORT) && (PJSIP_HAS_TLS_TRANSPORT != 0)
01374         case OPT_USE_TLS:
01375             cfg->use_tls = PJ_TRUE;
01376             break;
01377             
01378         case OPT_TLS_CA_FILE:
01379             cfg->udp_cfg.tls_setting.ca_list_file = pj_str(pj_optarg);
01380             break;
01381             
01382         case OPT_TLS_CERT_FILE:
01383             cfg->udp_cfg.tls_setting.cert_file = pj_str(pj_optarg);
01384             break;
01385             
01386         case OPT_TLS_PRIV_FILE:
01387             cfg->udp_cfg.tls_setting.privkey_file = pj_str(pj_optarg);
01388             break;
01389 
01390         case OPT_TLS_PASSWORD:
01391             cfg->udp_cfg.tls_setting.password = pj_str(pj_optarg);
01392             break;
01393 
01394         case OPT_TLS_VERIFY_SERVER:
01395             cfg->udp_cfg.tls_setting.verify_server = PJ_TRUE;
01396             break;
01397 
01398         case OPT_TLS_VERIFY_CLIENT:
01399             cfg->udp_cfg.tls_setting.verify_client = PJ_TRUE;
01400             cfg->udp_cfg.tls_setting.require_client_cert = PJ_TRUE;
01401             break;
01402 
01403         case OPT_TLS_NEG_TIMEOUT:
01404             cfg->udp_cfg.tls_setting.timeout.sec = atoi(pj_optarg);
01405             break;
01406 
01407         case OPT_TLS_CIPHER:
01408             {
01409                 pj_ssl_cipher cipher;
01410 
01411                 if (pj_ansi_strnicmp(pj_optarg, "0x", 2) == 0) {
01412                     pj_str_t cipher_st = pj_str(pj_optarg + 2);
01413                     cipher = pj_strtoul2(&cipher_st, NULL, 16);
01414                 } else {
01415                     cipher = atoi(pj_optarg);
01416                 }
01417 
01418                 if (pj_ssl_cipher_is_supported(cipher)) {
01419                     static pj_ssl_cipher tls_ciphers[128];
01420 
01421                     tls_ciphers[cfg->udp_cfg.tls_setting.ciphers_num++] = cipher;
01422                     cfg->udp_cfg.tls_setting.ciphers = tls_ciphers;
01423                 } else {
01424                     pj_ssl_cipher ciphers[128];
01425                     unsigned j, ciphers_cnt;
01426 
01427                     ciphers_cnt = PJ_ARRAY_SIZE(ciphers);
01428                     pj_ssl_cipher_get_availables(ciphers, &ciphers_cnt);
01429                     
01430                     PJ_LOG(1,(THIS_FILE, "Cipher \"%s\" is not supported by "
01431                                          "TLS/SSL backend.", pj_optarg));
01432                     printf("Available TLS/SSL ciphers (%d):\n", ciphers_cnt);
01433                     for (j=0; j<ciphers_cnt; ++j)
01434                         printf("- 0x%06X: %s\n", ciphers[j], pj_ssl_cipher_name(ciphers[j]));
01435                     return -1;
01436                 }
01437             }
01438             break;
01439 #endif /* PJSIP_HAS_TLS_TRANSPORT */
01440 
01441         case OPT_CAPTURE_DEV:
01442             cfg->capture_dev = atoi(pj_optarg);
01443             break;
01444 
01445         case OPT_PLAYBACK_DEV:
01446             cfg->playback_dev = atoi(pj_optarg);
01447             break;
01448 
01449         case OPT_STDOUT_REFRESH:
01450             stdout_refresh = atoi(pj_optarg);
01451             break;
01452 
01453         case OPT_STDOUT_REFRESH_TEXT:
01454             stdout_refresh_text = pj_optarg;
01455             break;
01456 
01457 #ifdef _IONBF
01458         case OPT_STDOUT_NO_BUF:
01459             setvbuf(stdout, NULL, _IONBF, 0);
01460             break;
01461 #endif
01462 
01463         case OPT_CAPTURE_LAT:
01464             cfg->capture_lat = atoi(pj_optarg);
01465             break;
01466 
01467         case OPT_PLAYBACK_LAT:
01468             cfg->playback_lat = atoi(pj_optarg);
01469             break;
01470 
01471         case OPT_SND_AUTO_CLOSE:
01472             cfg->media_cfg.snd_auto_close_time = atoi(pj_optarg);
01473             break;
01474 
01475         case OPT_NO_TONES:
01476             cfg->no_tones = PJ_TRUE;
01477             break;
01478 
01479         case OPT_JB_MAX_SIZE:
01480             cfg->media_cfg.jb_max = atoi(pj_optarg);
01481             break;
01482 
01483 #if defined(PJ_HAS_IPV6) && PJ_HAS_IPV6
01484         case OPT_IPV6:
01485             cfg->ipv6 = PJ_TRUE;
01486             break;
01487 #endif
01488         case OPT_QOS:
01489             cfg->enable_qos = PJ_TRUE;
01490             /* Set RTP traffic type to Voice */
01491             cfg->rtp_cfg.qos_type = PJ_QOS_TYPE_VOICE;
01492             /* Directly apply DSCP value to SIP traffic. Say lets
01493              * set it to CS3 (DSCP 011000). Note that this will not 
01494              * work on all platforms.
01495              */
01496             cfg->udp_cfg.qos_params.flags = PJ_QOS_PARAM_HAS_DSCP;
01497             cfg->udp_cfg.qos_params.dscp_val = 0x18;
01498             break;
01499         case OPT_VIDEO:
01500             cfg->vid.vid_cnt = 1;
01501             cfg->vid.in_auto_show = PJ_TRUE;
01502             cfg->vid.out_auto_transmit = PJ_TRUE;
01503             break;
01504         case OPT_EXTRA_AUDIO:
01505             cfg->aud_cnt++;
01506             break;
01507 
01508         case OPT_VCAPTURE_DEV:
01509             cfg->vid.vcapture_dev = atoi(pj_optarg);
01510             cur_acc->vid_cap_dev = cfg->vid.vcapture_dev;
01511             break;
01512 
01513         case OPT_VRENDER_DEV:
01514             cfg->vid.vrender_dev = atoi(pj_optarg);
01515             cur_acc->vid_rend_dev = cfg->vid.vrender_dev;
01516             break;
01517 
01518         case OPT_PLAY_AVI:
01519             if (app_config.avi_cnt >= MAX_AVI) {
01520                 PJ_LOG(1,(THIS_FILE, "Too many AVIs"));
01521                 return -1;
01522             }
01523             app_config.avi[app_config.avi_cnt++].path = pj_str(pj_optarg);
01524             break;
01525 
01526         case OPT_AUTO_PLAY_AVI:
01527             app_config.avi_auto_play = PJ_TRUE;
01528             break;
01529 
01530         default:
01531             PJ_LOG(1,(THIS_FILE, 
01532                       "Argument \"%s\" is not valid. Use --help to see help",
01533                       argv[pj_optind-1]));
01534             return -1;
01535         }
01536     }
01537 
01538     if (pj_optind != argc) {
01539         pj_str_t uri_arg;
01540 
01541         if (pjsua_verify_url(argv[pj_optind]) != PJ_SUCCESS) {
01542             PJ_LOG(1,(THIS_FILE, "Invalid SIP URI %s", argv[pj_optind]));
01543             return -1;
01544         }
01545         uri_arg = pj_str(argv[pj_optind]);
01546         if (uri_to_call)
01547             *uri_to_call = uri_arg;
01548         pj_optind++;
01549 
01550         /* Add URI to call to buddy list if it's not already there */
01551         for (i=0; i<cfg->buddy_cnt; ++i) {
01552             if (pj_stricmp(&cfg->buddy_cfg[i].uri, &uri_arg)==0)
01553                 break;
01554         }
01555         if (i == cfg->buddy_cnt && cfg->buddy_cnt < PJSUA_MAX_BUDDIES) {
01556             cfg->buddy_cfg[cfg->buddy_cnt++].uri = uri_arg;
01557         }
01558 
01559     } else {
01560         if (uri_to_call)
01561             uri_to_call->slen = 0;
01562     }
01563 
01564     if (pj_optind != argc) {
01565         PJ_LOG(1,(THIS_FILE, "Error: unknown options %s", argv[pj_optind]));
01566         return PJ_EINVAL;
01567     }
01568 
01569     if (cfg->acc_cfg[cfg->acc_cnt].id.slen)
01570         cfg->acc_cnt++;
01571 
01572     for (i=0; i<cfg->acc_cnt; ++i) {
01573         pjsua_acc_config *acfg = &cfg->acc_cfg[i];
01574 
01575         if (acfg->cred_info[acfg->cred_count].username.slen)
01576         {
01577             acfg->cred_count++;
01578         }
01579 
01580         /* When IMS mode is enabled for the account, verify that settings
01581          * are okay.
01582          */
01583         /* For now we check if IMS mode is activated by looking if
01584          * initial_auth is set.
01585          */
01586         if (acfg->auth_pref.initial_auth && acfg->cred_count) {
01587             /* Realm must point to the real domain */
01588             if (*acfg->cred_info[0].realm.ptr=='*') {
01589                 PJ_LOG(1,(THIS_FILE, 
01590                           "Error: cannot use '*' as realm with IMS"));
01591                 return PJ_EINVAL;
01592             }
01593 
01594             /* Username for authentication must be in a@b format */
01595             if (strchr(acfg->cred_info[0].username.ptr, '@')==0) {
01596                 PJ_LOG(1,(THIS_FILE, 
01597                           "Error: Username for authentication must "
01598                           "be in user@domain format with IMS"));
01599                 return PJ_EINVAL;
01600             }
01601         }
01602     }
01603 
01604 
01605     return PJ_SUCCESS;
01606 }
01607 
01608 
01609 /*
01610  * Save account settings
01611  */
01612 static void write_account_settings(int acc_index, pj_str_t *result)
01613 {
01614     unsigned i;
01615     char line[128];
01616     pjsua_acc_config *acc_cfg = &app_config.acc_cfg[acc_index];
01617 
01618     
01619     pj_ansi_sprintf(line, "\n#\n# Account %d:\n#\n", acc_index);
01620     pj_strcat2(result, line);
01621 
01622 
01623     /* Identity */
01624     if (acc_cfg->id.slen) {
01625         pj_ansi_sprintf(line, "--id %.*s\n", 
01626                         (int)acc_cfg->id.slen, 
01627                         acc_cfg->id.ptr);
01628         pj_strcat2(result, line);
01629     }
01630 
01631     /* Registrar server */
01632     if (acc_cfg->reg_uri.slen) {
01633         pj_ansi_sprintf(line, "--registrar %.*s\n",
01634                               (int)acc_cfg->reg_uri.slen,
01635                               acc_cfg->reg_uri.ptr);
01636         pj_strcat2(result, line);
01637 
01638         pj_ansi_sprintf(line, "--reg-timeout %u\n",
01639                               acc_cfg->reg_timeout);
01640         pj_strcat2(result, line);
01641     }
01642 
01643     /* Contact */
01644     if (acc_cfg->force_contact.slen) {
01645         pj_ansi_sprintf(line, "--contact %.*s\n", 
01646                         (int)acc_cfg->force_contact.slen, 
01647                         acc_cfg->force_contact.ptr);
01648         pj_strcat2(result, line);
01649     }
01650 
01651     /* Contact header parameters */
01652     if (acc_cfg->contact_params.slen) {
01653         pj_ansi_sprintf(line, "--contact-params %.*s\n", 
01654                         (int)acc_cfg->contact_params.slen, 
01655                         acc_cfg->contact_params.ptr);
01656         pj_strcat2(result, line);
01657     }
01658 
01659     /* Contact URI parameters */
01660     if (acc_cfg->contact_uri_params.slen) {
01661         pj_ansi_sprintf(line, "--contact-uri-params %.*s\n", 
01662                         (int)acc_cfg->contact_uri_params.slen, 
01663                         acc_cfg->contact_uri_params.ptr);
01664         pj_strcat2(result, line);
01665     }
01666 
01667     /*  */
01668     if (acc_cfg->allow_contact_rewrite!=1)
01669     {
01670         pj_ansi_sprintf(line, "--auto-update-nat %i\n",
01671                         (int)acc_cfg->allow_contact_rewrite);
01672         pj_strcat2(result, line);
01673     }
01674 
01675 #if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
01676     /* SRTP */
01677     if (acc_cfg->use_srtp) {
01678         int use_srtp = (int)acc_cfg->use_srtp;
01679         if (use_srtp == PJMEDIA_SRTP_OPTIONAL && 
01680             acc_cfg->srtp_optional_dup_offer)
01681         {
01682             use_srtp = 3;
01683         }
01684         pj_ansi_sprintf(line, "--use-srtp %i\n", use_srtp);
01685         pj_strcat2(result, line);
01686     }
01687     if (acc_cfg->srtp_secure_signaling != 
01688         PJSUA_DEFAULT_SRTP_SECURE_SIGNALING) 
01689     {
01690         pj_ansi_sprintf(line, "--srtp-secure %d\n",
01691                         acc_cfg->srtp_secure_signaling);
01692         pj_strcat2(result, line);
01693     }
01694 #endif
01695 
01696     /* Proxy */
01697     for (i=0; i<acc_cfg->proxy_cnt; ++i) {
01698         pj_ansi_sprintf(line, "--proxy %.*s\n",
01699                               (int)acc_cfg->proxy[i].slen,
01700                               acc_cfg->proxy[i].ptr);
01701         pj_strcat2(result, line);
01702     }
01703 
01704     /* Credentials */
01705     for (i=0; i<acc_cfg->cred_count; ++i) {
01706         if (acc_cfg->cred_info[i].realm.slen) {
01707             pj_ansi_sprintf(line, "--realm %.*s\n",
01708                                   (int)acc_cfg->cred_info[i].realm.slen,
01709                                   acc_cfg->cred_info[i].realm.ptr);
01710             pj_strcat2(result, line);
01711         }
01712 
01713         if (acc_cfg->cred_info[i].username.slen) {
01714             pj_ansi_sprintf(line, "--username %.*s\n",
01715                                   (int)acc_cfg->cred_info[i].username.slen,
01716                                   acc_cfg->cred_info[i].username.ptr);
01717             pj_strcat2(result, line);
01718         }
01719 
01720         if (acc_cfg->cred_info[i].data.slen) {
01721             pj_ansi_sprintf(line, "--password %.*s\n",
01722                                   (int)acc_cfg->cred_info[i].data.slen,
01723                                   acc_cfg->cred_info[i].data.ptr);
01724             pj_strcat2(result, line);
01725         }
01726 
01727         if (i != acc_cfg->cred_count - 1)
01728             pj_strcat2(result, "--next-cred\n");
01729     }
01730 
01731     /* reg-use-proxy */
01732     if (acc_cfg->reg_use_proxy != 3) {
01733         pj_ansi_sprintf(line, "--reg-use-proxy %d\n",
01734                               acc_cfg->reg_use_proxy);
01735         pj_strcat2(result, line);
01736     }
01737 
01738     /* rereg-delay */
01739     if (acc_cfg->reg_retry_interval != PJSUA_REG_RETRY_INTERVAL) {
01740         pj_ansi_sprintf(line, "--rereg-delay %d\n",
01741                               acc_cfg->reg_retry_interval);
01742         pj_strcat2(result, line);
01743     }
01744 
01745     /* 100rel extension */
01746     if (acc_cfg->require_100rel) {
01747         pj_strcat2(result, "--use-100rel\n");
01748     }
01749 
01750     /* Session Timer extension */
01751     if (acc_cfg->use_timer) {
01752         pj_ansi_sprintf(line, "--use-timer %d\n",
01753                               acc_cfg->use_timer);
01754         pj_strcat2(result, line);
01755     }
01756     if (acc_cfg->timer_setting.min_se != 90) {
01757         pj_ansi_sprintf(line, "--timer-min-se %d\n",
01758                               acc_cfg->timer_setting.min_se);
01759         pj_strcat2(result, line);
01760     }
01761     if (acc_cfg->timer_setting.sess_expires != PJSIP_SESS_TIMER_DEF_SE) {
01762         pj_ansi_sprintf(line, "--timer-se %d\n",
01763                               acc_cfg->timer_setting.sess_expires);
01764         pj_strcat2(result, line);
01765     }
01766 
01767     /* Publish */
01768     if (acc_cfg->publish_enabled)
01769         pj_strcat2(result, "--publish\n");
01770 
01771     /* MWI */
01772     if (acc_cfg->mwi_enabled)
01773         pj_strcat2(result, "--mwi\n");
01774 }
01775 
01776 
01777 /*
01778  * Write settings.
01779  */
01780 static int write_settings(const struct app_config *config,
01781                           char *buf, pj_size_t max)
01782 {
01783     unsigned acc_index;
01784     unsigned i;
01785     pj_str_t cfg;
01786     char line[128];
01787     extern pj_bool_t pjsip_use_compact_form;
01788 
01789     PJ_UNUSED_ARG(max);
01790 
01791     cfg.ptr = buf;
01792     cfg.slen = 0;
01793 
01794     /* Logging. */
01795     pj_strcat2(&cfg, "#\n# Logging options:\n#\n");
01796     pj_ansi_sprintf(line, "--log-level %d\n",
01797                     config->log_cfg.level);
01798     pj_strcat2(&cfg, line);
01799 
01800     pj_ansi_sprintf(line, "--app-log-level %d\n",
01801                     config->log_cfg.console_level);
01802     pj_strcat2(&cfg, line);
01803 
01804     if (config->log_cfg.log_filename.slen) {
01805         pj_ansi_sprintf(line, "--log-file %.*s\n",
01806                         (int)config->log_cfg.log_filename.slen,
01807                         config->log_cfg.log_filename.ptr);
01808         pj_strcat2(&cfg, line);
01809     }
01810 
01811     if (config->log_cfg.log_file_flags & PJ_O_APPEND) {
01812         pj_strcat2(&cfg, "--log-append\n");
01813     }
01814 
01815     /* Save account settings. */
01816     for (acc_index=0; acc_index < config->acc_cnt; ++acc_index) {
01817         
01818         write_account_settings(acc_index, &cfg);
01819 
01820         if (acc_index < config->acc_cnt-1)
01821             pj_strcat2(&cfg, "--next-account\n");
01822     }
01823 
01824 
01825     pj_strcat2(&cfg, "\n#\n# Network settings:\n#\n");
01826 
01827     /* Nameservers */
01828     for (i=0; i<config->cfg.nameserver_count; ++i) {
01829         pj_ansi_sprintf(line, "--nameserver %.*s\n",
01830                               (int)config->cfg.nameserver[i].slen,
01831                               config->cfg.nameserver[i].ptr);
01832         pj_strcat2(&cfg, line);
01833     }
01834 
01835     /* Outbound proxy */
01836     for (i=0; i<config->cfg.outbound_proxy_cnt; ++i) {
01837         pj_ansi_sprintf(line, "--outbound %.*s\n",
01838                               (int)config->cfg.outbound_proxy[i].slen,
01839                               config->cfg.outbound_proxy[i].ptr);
01840         pj_strcat2(&cfg, line);
01841     }
01842 
01843     /* Transport options */
01844     if (config->ipv6) {
01845         pj_strcat2(&cfg, "--ipv6\n");
01846     }
01847     if (config->enable_qos) {
01848         pj_strcat2(&cfg, "--set-qos\n");
01849     }
01850 
01851     /* UDP Transport. */
01852     pj_ansi_sprintf(line, "--local-port %d\n", config->udp_cfg.port);
01853     pj_strcat2(&cfg, line);
01854 
01855     /* IP address, if any. */
01856     if (config->udp_cfg.public_addr.slen) {
01857         pj_ansi_sprintf(line, "--ip-addr %.*s\n", 
01858                         (int)config->udp_cfg.public_addr.slen,
01859                         config->udp_cfg.public_addr.ptr);
01860         pj_strcat2(&cfg, line);
01861     }
01862 
01863     /* Bound IP address, if any. */
01864     if (config->udp_cfg.bound_addr.slen) {
01865         pj_ansi_sprintf(line, "--bound-addr %.*s\n", 
01866                         (int)config->udp_cfg.bound_addr.slen,
01867                         config->udp_cfg.bound_addr.ptr);
01868         pj_strcat2(&cfg, line);
01869     }
01870 
01871     /* No TCP ? */
01872     if (config->no_tcp) {
01873         pj_strcat2(&cfg, "--no-tcp\n");
01874     }
01875 
01876     /* No UDP ? */
01877     if (config->no_udp) {
01878         pj_strcat2(&cfg, "--no-udp\n");
01879     }
01880 
01881     /* STUN */
01882     for (i=0; i<config->cfg.stun_srv_cnt; ++i) {
01883         pj_ansi_sprintf(line, "--stun-srv %.*s\n",
01884                         (int)config->cfg.stun_srv[i].slen, 
01885                         config->cfg.stun_srv[i].ptr);
01886         pj_strcat2(&cfg, line);
01887     }
01888 
01889 #if defined(PJSIP_HAS_TLS_TRANSPORT) && (PJSIP_HAS_TLS_TRANSPORT != 0)
01890     /* TLS */
01891     if (config->use_tls)
01892         pj_strcat2(&cfg, "--use-tls\n");
01893     if (config->udp_cfg.tls_setting.ca_list_file.slen) {
01894         pj_ansi_sprintf(line, "--tls-ca-file %.*s\n",
01895                         (int)config->udp_cfg.tls_setting.ca_list_file.slen, 
01896                         config->udp_cfg.tls_setting.ca_list_file.ptr);
01897         pj_strcat2(&cfg, line);
01898     }
01899     if (config->udp_cfg.tls_setting.cert_file.slen) {
01900         pj_ansi_sprintf(line, "--tls-cert-file %.*s\n",
01901                         (int)config->udp_cfg.tls_setting.cert_file.slen, 
01902                         config->udp_cfg.tls_setting.cert_file.ptr);
01903         pj_strcat2(&cfg, line);
01904     }
01905     if (config->udp_cfg.tls_setting.privkey_file.slen) {
01906         pj_ansi_sprintf(line, "--tls-privkey-file %.*s\n",
01907                         (int)config->udp_cfg.tls_setting.privkey_file.slen, 
01908                         config->udp_cfg.tls_setting.privkey_file.ptr);
01909         pj_strcat2(&cfg, line);
01910     }
01911 
01912     if (config->udp_cfg.tls_setting.password.slen) {
01913         pj_ansi_sprintf(line, "--tls-password %.*s\n",
01914                         (int)config->udp_cfg.tls_setting.password.slen, 
01915                         config->udp_cfg.tls_setting.password.ptr);
01916         pj_strcat2(&cfg, line);
01917     }
01918 
01919     if (config->udp_cfg.tls_setting.verify_server)
01920         pj_strcat2(&cfg, "--tls-verify-server\n");
01921 
01922     if (config->udp_cfg.tls_setting.verify_client)
01923         pj_strcat2(&cfg, "--tls-verify-client\n");
01924 
01925     if (config->udp_cfg.tls_setting.timeout.sec) {
01926         pj_ansi_sprintf(line, "--tls-neg-timeout %d\n",
01927                         (int)config->udp_cfg.tls_setting.timeout.sec);
01928         pj_strcat2(&cfg, line);
01929     }
01930 
01931     for (i=0; i<config->udp_cfg.tls_setting.ciphers_num; ++i) {
01932         pj_ansi_sprintf(line, "--tls-cipher 0x%06X # %s\n",
01933                         config->udp_cfg.tls_setting.ciphers[i],
01934                         pj_ssl_cipher_name(config->udp_cfg.tls_setting.ciphers[i]));
01935         pj_strcat2(&cfg, line);
01936     }
01937 #endif
01938 
01939     pj_strcat2(&cfg, "\n#\n# Media settings:\n#\n");
01940 
01941     /* Video & extra audio */
01942     for (i=0; i<config->vid.vid_cnt; ++i) {
01943         pj_strcat2(&cfg, "--video\n");
01944     }
01945     for (i=1; i<config->aud_cnt; ++i) {
01946         pj_strcat2(&cfg, "--extra-audio\n");
01947     }
01948 
01949     /* SRTP */
01950 #if PJMEDIA_HAS_SRTP
01951     if (app_config.cfg.use_srtp != PJSUA_DEFAULT_USE_SRTP) {
01952         int use_srtp = (int)app_config.cfg.use_srtp;
01953         if (use_srtp == PJMEDIA_SRTP_OPTIONAL && 
01954             app_config.cfg.srtp_optional_dup_offer)
01955         {
01956             use_srtp = 3;
01957         }
01958         pj_ansi_sprintf(line, "--use-srtp %d\n", use_srtp);
01959         pj_strcat2(&cfg, line);
01960     }
01961     if (app_config.cfg.srtp_secure_signaling != 
01962         PJSUA_DEFAULT_SRTP_SECURE_SIGNALING) 
01963     {
01964         pj_ansi_sprintf(line, "--srtp-secure %d\n",
01965                         app_config.cfg.srtp_secure_signaling);
01966         pj_strcat2(&cfg, line);
01967     }
01968 #endif
01969 
01970     /* Media Transport*/
01971     if (config->media_cfg.enable_ice)
01972         pj_strcat2(&cfg, "--use-ice\n");
01973 
01974     if (config->media_cfg.ice_opt.aggressive == PJ_FALSE)
01975         pj_strcat2(&cfg, "--ice-regular\n");
01976 
01977     if (config->media_cfg.enable_turn)
01978         pj_strcat2(&cfg, "--use-turn\n");
01979 
01980     if (config->media_cfg.ice_max_host_cands >= 0) {
01981         pj_ansi_sprintf(line, "--ice_max_host_cands %d\n",
01982                         config->media_cfg.ice_max_host_cands);
01983         pj_strcat2(&cfg, line);
01984     }
01985 
01986     if (config->media_cfg.ice_no_rtcp)
01987         pj_strcat2(&cfg, "--ice-no-rtcp\n");
01988 
01989     if (config->media_cfg.turn_server.slen) {
01990         pj_ansi_sprintf(line, "--turn-srv %.*s\n",
01991                         (int)config->media_cfg.turn_server.slen,
01992                         config->media_cfg.turn_server.ptr);
01993         pj_strcat2(&cfg, line);
01994     }
01995 
01996     if (config->media_cfg.turn_conn_type == PJ_TURN_TP_TCP)
01997         pj_strcat2(&cfg, "--turn-tcp\n");
01998 
01999     if (config->media_cfg.turn_auth_cred.data.static_cred.username.slen) {
02000         pj_ansi_sprintf(line, "--turn-user %.*s\n",
02001                         (int)config->media_cfg.turn_auth_cred.data.static_cred.username.slen,
02002                         config->media_cfg.turn_auth_cred.data.static_cred.username.ptr);
02003         pj_strcat2(&cfg, line);
02004     }
02005 
02006     if (config->media_cfg.turn_auth_cred.data.static_cred.data.slen) {
02007         pj_ansi_sprintf(line, "--turn-passwd %.*s\n",
02008                         (int)config->media_cfg.turn_auth_cred.data.static_cred.data.slen,
02009                         config->media_cfg.turn_auth_cred.data.static_cred.data.ptr);
02010         pj_strcat2(&cfg, line);
02011     }
02012 
02013     /* Media */
02014     if (config->null_audio)
02015         pj_strcat2(&cfg, "--null-audio\n");
02016     if (config->auto_play)
02017         pj_strcat2(&cfg, "--auto-play\n");
02018     if (config->auto_loop)
02019         pj_strcat2(&cfg, "--auto-loop\n");
02020     if (config->auto_conf)
02021         pj_strcat2(&cfg, "--auto-conf\n");
02022     for (i=0; i<config->wav_count; ++i) {
02023         pj_ansi_sprintf(line, "--play-file %s\n",
02024                         config->wav_files[i].ptr);
02025         pj_strcat2(&cfg, line);
02026     }
02027     for (i=0; i<config->tone_count; ++i) {
02028         pj_ansi_sprintf(line, "--play-tone %d,%d,%d,%d\n",
02029                         config->tones[i].freq1, config->tones[i].freq2, 
02030                         config->tones[i].on_msec, config->tones[i].off_msec);
02031         pj_strcat2(&cfg, line);
02032     }
02033     if (config->rec_file.slen) {
02034         pj_ansi_sprintf(line, "--rec-file %s\n",
02035                         config->rec_file.ptr);
02036         pj_strcat2(&cfg, line);
02037     }
02038     if (config->auto_rec)
02039         pj_strcat2(&cfg, "--auto-rec\n");
02040     if (config->capture_dev != PJSUA_INVALID_ID) {
02041         pj_ansi_sprintf(line, "--capture-dev %d\n", config->capture_dev);
02042         pj_strcat2(&cfg, line);
02043     }
02044     if (config->playback_dev != PJSUA_INVALID_ID) {
02045         pj_ansi_sprintf(line, "--playback-dev %d\n", config->playback_dev);
02046         pj_strcat2(&cfg, line);
02047     }
02048     if (config->media_cfg.snd_auto_close_time != -1) {
02049         pj_ansi_sprintf(line, "--snd-auto-close %d\n", 
02050                         config->media_cfg.snd_auto_close_time);
02051         pj_strcat2(&cfg, line);
02052     }
02053     if (config->no_tones) {
02054         pj_strcat2(&cfg, "--no-tones\n");
02055     }
02056     if (config->media_cfg.jb_max != -1) {
02057         pj_ansi_sprintf(line, "--jb-max-size %d\n", 
02058                         config->media_cfg.jb_max);
02059         pj_strcat2(&cfg, line);
02060     }
02061 
02062     /* Sound device latency */
02063     if (config->capture_lat != PJMEDIA_SND_DEFAULT_REC_LATENCY) {
02064         pj_ansi_sprintf(line, "--capture-lat %d\n", config->capture_lat);
02065         pj_strcat2(&cfg, line);
02066     }
02067     if (config->playback_lat != PJMEDIA_SND_DEFAULT_PLAY_LATENCY) {
02068         pj_ansi_sprintf(line, "--playback-lat %d\n", config->playback_lat);
02069         pj_strcat2(&cfg, line);
02070     }
02071 
02072     /* Media clock rate. */
02073     if (config->media_cfg.clock_rate != PJSUA_DEFAULT_CLOCK_RATE) {
02074         pj_ansi_sprintf(line, "--clock-rate %d\n",
02075                         config->media_cfg.clock_rate);
02076         pj_strcat2(&cfg, line);
02077     } else {
02078         pj_ansi_sprintf(line, "#using default --clock-rate %d\n",
02079                         config->media_cfg.clock_rate);
02080         pj_strcat2(&cfg, line);
02081     }
02082 
02083     if (config->media_cfg.snd_clock_rate && 
02084         config->media_cfg.snd_clock_rate != config->media_cfg.clock_rate) 
02085     {
02086         pj_ansi_sprintf(line, "--snd-clock-rate %d\n",
02087                         config->media_cfg.snd_clock_rate);
02088         pj_strcat2(&cfg, line);
02089     }
02090 
02091     /* Stereo mode. */
02092     if (config->media_cfg.channel_count == 2) {
02093         pj_ansi_sprintf(line, "--stereo\n");
02094         pj_strcat2(&cfg, line);
02095     }
02096 
02097     /* quality */
02098     if (config->media_cfg.quality != PJSUA_DEFAULT_CODEC_QUALITY) {
02099         pj_ansi_sprintf(line, "--quality %d\n",
02100                         config->media_cfg.quality);
02101         pj_strcat2(&cfg, line);
02102     } else {
02103         pj_ansi_sprintf(line, "#using default --quality %d\n",
02104                         config->media_cfg.quality);
02105         pj_strcat2(&cfg, line);
02106     }
02107 
02108     if (config->vid.vcapture_dev != PJMEDIA_VID_DEFAULT_CAPTURE_DEV) {
02109         pj_ansi_sprintf(line, "--vcapture-dev %d\n", config->vid.vcapture_dev);
02110         pj_strcat2(&cfg, line);
02111     }
02112     if (config->vid.vrender_dev != PJMEDIA_VID_DEFAULT_RENDER_DEV) {
02113         pj_ansi_sprintf(line, "--vrender-dev %d\n", config->vid.vrender_dev);
02114         pj_strcat2(&cfg, line);
02115     }
02116     for (i=0; i<config->avi_cnt; ++i) {
02117         pj_ansi_sprintf(line, "--play-avi %s\n", config->avi[i].path.ptr);
02118         pj_strcat2(&cfg, line);
02119     }
02120     if (config->avi_auto_play) {
02121         pj_ansi_sprintf(line, "--auto-play-avi\n");
02122         pj_strcat2(&cfg, line);
02123     }
02124 
02125     /* ptime */
02126     if (config->media_cfg.ptime) {
02127         pj_ansi_sprintf(line, "--ptime %d\n",
02128                         config->media_cfg.ptime);
02129         pj_strcat2(&cfg, line);
02130     }
02131 
02132     /* no-vad */
02133     if (config->media_cfg.no_vad) {
02134         pj_strcat2(&cfg, "--no-vad\n");
02135     }
02136 
02137     /* ec-tail */
02138     if (config->media_cfg.ec_tail_len != PJSUA_DEFAULT_EC_TAIL_LEN) {
02139         pj_ansi_sprintf(line, "--ec-tail %d\n",
02140                         config->media_cfg.ec_tail_len);
02141         pj_strcat2(&cfg, line);
02142     } else {
02143         pj_ansi_sprintf(line, "#using default --ec-tail %d\n",
02144                         config->media_cfg.ec_tail_len);
02145         pj_strcat2(&cfg, line);
02146     }
02147 
02148     /* ec-opt */
02149     if (config->media_cfg.ec_options != 0) {
02150         pj_ansi_sprintf(line, "--ec-opt %d\n",
02151                         config->media_cfg.ec_options);
02152         pj_strcat2(&cfg, line);
02153     } 
02154 
02155     /* ilbc-mode */
02156     if (config->media_cfg.ilbc_mode != PJSUA_DEFAULT_ILBC_MODE) {
02157         pj_ansi_sprintf(line, "--ilbc-mode %d\n",
02158                         config->media_cfg.ilbc_mode);
02159         pj_strcat2(&cfg, line);
02160     } else {
02161         pj_ansi_sprintf(line, "#using default --ilbc-mode %d\n",
02162                         config->media_cfg.ilbc_mode);
02163         pj_strcat2(&cfg, line);
02164     }
02165 
02166     /* RTP drop */
02167     if (config->media_cfg.tx_drop_pct) {
02168         pj_ansi_sprintf(line, "--tx-drop-pct %d\n",
02169                         config->media_cfg.tx_drop_pct);
02170         pj_strcat2(&cfg, line);
02171 
02172     }
02173     if (config->media_cfg.rx_drop_pct) {
02174         pj_ansi_sprintf(line, "--rx-drop-pct %d\n",
02175                         config->media_cfg.rx_drop_pct);
02176         pj_strcat2(&cfg, line);
02177 
02178     }
02179 
02180 
02181     /* Start RTP port. */
02182     pj_ansi_sprintf(line, "--rtp-port %d\n",
02183                     config->rtp_cfg.port);
02184     pj_strcat2(&cfg, line);
02185 
02186     /* Disable codec */
02187     for (i=0; i<config->codec_dis_cnt; ++i) {
02188         pj_ansi_sprintf(line, "--dis-codec %s\n",
02189                     config->codec_dis[i].ptr);
02190         pj_strcat2(&cfg, line);
02191     }
02192     /* Add codec. */
02193     for (i=0; i<config->codec_cnt; ++i) {
02194         pj_ansi_sprintf(line, "--add-codec %s\n",
02195                     config->codec_arg[i].ptr);
02196         pj_strcat2(&cfg, line);
02197     }
02198 
02199     pj_strcat2(&cfg, "\n#\n# User agent:\n#\n");
02200 
02201     /* Auto-answer. */
02202     if (config->auto_answer != 0) {
02203         pj_ansi_sprintf(line, "--auto-answer %d\n",
02204                         config->auto_answer);
02205         pj_strcat2(&cfg, line);
02206     }
02207 
02208     /* accept-redirect */
02209     if (config->redir_op != PJSIP_REDIRECT_ACCEPT) {
02210         pj_ansi_sprintf(line, "--accept-redirect %d\n",
02211                         config->redir_op);
02212         pj_strcat2(&cfg, line);
02213     }
02214 
02215     /* Max calls. */
02216     pj_ansi_sprintf(line, "--max-calls %d\n",
02217                     config->cfg.max_calls);
02218     pj_strcat2(&cfg, line);
02219 
02220     /* Uas-duration. */
02221     if (config->duration != NO_LIMIT) {
02222         pj_ansi_sprintf(line, "--duration %d\n",
02223                         config->duration);
02224         pj_strcat2(&cfg, line);
02225     }
02226 
02227     /* norefersub ? */
02228     if (config->no_refersub) {
02229         pj_strcat2(&cfg, "--norefersub\n");
02230     }
02231 
02232     if (pjsip_use_compact_form)
02233     {
02234         pj_strcat2(&cfg, "--use-compact-form\n");
02235     }
02236 
02237     if (!config->cfg.force_lr) {
02238         pj_strcat2(&cfg, "--no-force-lr\n");
02239     }
02240 
02241     pj_strcat2(&cfg, "\n#\n# Buddies:\n#\n");
02242 
02243     /* Add buddies. */
02244     for (i=0; i<config->buddy_cnt; ++i) {
02245         pj_ansi_sprintf(line, "--add-buddy %.*s\n",
02246                               (int)config->buddy_cfg[i].uri.slen,
02247                               config->buddy_cfg[i].uri.ptr);
02248         pj_strcat2(&cfg, line);
02249     }
02250 
02251     /* SIP extensions. */
02252     pj_strcat2(&cfg, "\n#\n# SIP extensions:\n#\n");
02253     /* 100rel extension */
02254     if (config->cfg.require_100rel) {
02255         pj_strcat2(&cfg, "--use-100rel\n");
02256     }
02257     /* Session Timer extension */
02258     if (config->cfg.use_timer) {
02259         pj_ansi_sprintf(line, "--use-timer %d\n",
02260                               config->cfg.use_timer);
02261         pj_strcat2(&cfg, line);
02262     }
02263     if (config->cfg.timer_setting.min_se != 90) {
02264         pj_ansi_sprintf(line, "--timer-min-se %d\n",
02265                               config->cfg.timer_setting.min_se);
02266         pj_strcat2(&cfg, line);
02267     }
02268     if (config->cfg.timer_setting.sess_expires != PJSIP_SESS_TIMER_DEF_SE) {
02269         pj_ansi_sprintf(line, "--timer-se %d\n",
02270                               config->cfg.timer_setting.sess_expires);
02271         pj_strcat2(&cfg, line);
02272     }
02273 
02274     *(cfg.ptr + cfg.slen) = '\0';
02275     return cfg.slen;
02276 }
02277 
02278 
02279 /*
02280  * Dump application states.
02281  */
02282 static void app_dump(pj_bool_t detail)
02283 {
02284     pjsua_dump(detail);
02285 }
02286 
02287 /*
02288  * Print log of call states. Since call states may be too long for logger,
02289  * printing it is a bit tricky, it should be printed part by part as long 
02290  * as the logger can accept.
02291  */
02292 static void log_call_dump(int call_id) 
02293 {
02294     unsigned call_dump_len;
02295     unsigned part_len;
02296     unsigned part_idx;
02297     unsigned log_decor;
02298 
02299     pjsua_call_dump(call_id, PJ_TRUE, some_buf, 
02300                     sizeof(some_buf), "  ");
02301     call_dump_len = strlen(some_buf);
02302 
02303     log_decor = pj_log_get_decor();
02304     pj_log_set_decor(log_decor & ~(PJ_LOG_HAS_NEWLINE | PJ_LOG_HAS_CR));
02305     PJ_LOG(3,(THIS_FILE, "\n"));
02306     pj_log_set_decor(0);
02307 
02308     part_idx = 0;
02309     part_len = PJ_LOG_MAX_SIZE-80;
02310     while (part_idx < call_dump_len) {
02311         char p_orig, *p;
02312 
02313         p = &some_buf[part_idx];
02314         if (part_idx + part_len > call_dump_len)
02315             part_len = call_dump_len - part_idx;
02316         p_orig = p[part_len];
02317         p[part_len] = '\0';
02318         PJ_LOG(3,(THIS_FILE, "%s", p));
02319         p[part_len] = p_orig;
02320         part_idx += part_len;
02321     }
02322     pj_log_set_decor(log_decor);
02323 }
02324 
02325 /*****************************************************************************
02326  * Console application
02327  */
02328 
02329 static void ringback_start(pjsua_call_id call_id)
02330 {
02331     if (app_config.no_tones)
02332         return;
02333 
02334     if (app_config.call_data[call_id].ringback_on)
02335         return;
02336 
02337     app_config.call_data[call_id].ringback_on = PJ_TRUE;
02338 
02339     if (++app_config.ringback_cnt==1 && 
02340         app_config.ringback_slot!=PJSUA_INVALID_ID) 
02341     {
02342         pjsua_conf_connect(app_config.ringback_slot, 0);
02343     }
02344 }
02345 
02346 static void ring_stop(pjsua_call_id call_id)
02347 {
02348     if (app_config.no_tones)
02349         return;
02350 
02351     if (app_config.call_data[call_id].ringback_on) {
02352         app_config.call_data[call_id].ringback_on = PJ_FALSE;
02353 
02354         pj_assert(app_config.ringback_cnt>0);
02355         if (--app_config.ringback_cnt == 0 && 
02356             app_config.ringback_slot!=PJSUA_INVALID_ID) 
02357         {
02358             pjsua_conf_disconnect(app_config.ringback_slot, 0);
02359             pjmedia_tonegen_rewind(app_config.ringback_port);
02360         }
02361     }
02362 
02363     if (app_config.call_data[call_id].ring_on) {
02364         app_config.call_data[call_id].ring_on = PJ_FALSE;
02365 
02366         pj_assert(app_config.ring_cnt>0);
02367         if (--app_config.ring_cnt == 0 && 
02368             app_config.ring_slot!=PJSUA_INVALID_ID) 
02369         {
02370             pjsua_conf_disconnect(app_config.ring_slot, 0);
02371             pjmedia_tonegen_rewind(app_config.ring_port);
02372         }
02373     }
02374 }
02375 
02376 static void ring_start(pjsua_call_id call_id)
02377 {
02378     if (app_config.no_tones)
02379         return;
02380 
02381     if (app_config.call_data[call_id].ring_on)
02382         return;
02383 
02384     app_config.call_data[call_id].ring_on = PJ_TRUE;
02385 
02386     if (++app_config.ring_cnt==1 && 
02387         app_config.ring_slot!=PJSUA_INVALID_ID) 
02388     {
02389         pjsua_conf_connect(app_config.ring_slot, 0);
02390     }
02391 }
02392 
02393 #ifdef HAVE_MULTIPART_TEST
02394   /*
02395    * Enable multipart in msg_data and add a dummy body into the
02396    * multipart bodies.
02397    */
02398   static void add_multipart(pjsua_msg_data *msg_data)
02399   {
02400       static pjsip_multipart_part *alt_part;
02401 
02402       if (!alt_part) {
02403           pj_str_t type, subtype, content;
02404 
02405           alt_part = pjsip_multipart_create_part(app_config.pool);
02406 
02407           type = pj_str("text");
02408           subtype = pj_str("plain");
02409           content = pj_str("Sample text body of a multipart bodies");
02410           alt_part->body = pjsip_msg_body_create(app_config.pool, &type,
02411                                                  &subtype, &content);
02412       }
02413 
02414       msg_data->multipart_ctype.type = pj_str("multipart");
02415       msg_data->multipart_ctype.subtype = pj_str("mixed");
02416       pj_list_push_back(&msg_data->multipart_parts, alt_part);
02417   }
02418 #  define TEST_MULTIPART(msg_data)      add_multipart(msg_data)
02419 #else
02420 #  define TEST_MULTIPART(msg_data)
02421 #endif
02422 
02423 /*
02424  * Find next call when current call is disconnected or when user
02425  * press ']'
02426  */
02427 static pj_bool_t find_next_call(void)
02428 {
02429     int i, max;
02430 
02431     max = pjsua_call_get_max_count();
02432     for (i=current_call+1; i<max; ++i) {
02433         if (pjsua_call_is_active(i)) {
02434             current_call = i;
02435             return PJ_TRUE;
02436         }
02437     }
02438 
02439     for (i=0; i<current_call; ++i) {
02440         if (pjsua_call_is_active(i)) {
02441             current_call = i;
02442             return PJ_TRUE;
02443         }
02444     }
02445 
02446     current_call = PJSUA_INVALID_ID;
02447     return PJ_FALSE;
02448 }
02449 
02450 
02451 /*
02452  * Find previous call when user press '['
02453  */
02454 static pj_bool_t find_prev_call(void)
02455 {
02456     int i, max;
02457 
02458     max = pjsua_call_get_max_count();
02459     for (i=current_call-1; i>=0; --i) {
02460         if (pjsua_call_is_active(i)) {
02461             current_call = i;
02462             return PJ_TRUE;
02463         }
02464     }
02465 
02466     for (i=max-1; i>current_call; --i) {
02467         if (pjsua_call_is_active(i)) {
02468             current_call = i;
02469             return PJ_TRUE;
02470         }
02471     }
02472 
02473     current_call = PJSUA_INVALID_ID;
02474     return PJ_FALSE;
02475 }
02476 
02477 
02478 /* Callback from timer when the maximum call duration has been
02479  * exceeded.
02480  */
02481 static void call_timeout_callback(pj_timer_heap_t *timer_heap,
02482                                   struct pj_timer_entry *entry)
02483 {
02484     pjsua_call_id call_id = entry->id;
02485     pjsua_msg_data msg_data;
02486     pjsip_generic_string_hdr warn;
02487     pj_str_t hname = pj_str("Warning");
02488     pj_str_t hvalue = pj_str("399 pjsua \"Call duration exceeded\"");
02489 
02490     PJ_UNUSED_ARG(timer_heap);
02491 
02492     if (call_id == PJSUA_INVALID_ID) {
02493         PJ_LOG(1,(THIS_FILE, "Invalid call ID in timer callback"));
02494         return;
02495     }
02496     
02497     /* Add warning header */
02498     pjsua_msg_data_init(&msg_data);
02499     pjsip_generic_string_hdr_init2(&warn, &hname, &hvalue);
02500     pj_list_push_back(&msg_data.hdr_list, &warn);
02501 
02502     /* Call duration has been exceeded; disconnect the call */
02503     PJ_LOG(3,(THIS_FILE, "Duration (%d seconds) has been exceeded "
02504                          "for call %d, disconnecting the call",
02505                          app_config.duration, call_id));
02506     entry->id = PJSUA_INVALID_ID;
02507     pjsua_call_hangup(call_id, 200, NULL, &msg_data);
02508 }
02509 
02510 
02511 /*
02512  * Handler when invite state has changed.
02513  */
02514 static void on_call_state(pjsua_call_id call_id, pjsip_event *e)
02515 {
02516     pjsua_call_info call_info;
02517 
02518     PJ_UNUSED_ARG(e);
02519 
02520     pjsua_call_get_info(call_id, &call_info);
02521 
02522     if (call_info.state == PJSIP_INV_STATE_DISCONNECTED) {
02523 
02524         /* Stop all ringback for this call */
02525         ring_stop(call_id);
02526 
02527         /* Cancel duration timer, if any */
02528         if (app_config.call_data[call_id].timer.id != PJSUA_INVALID_ID) {
02529             struct call_data *cd = &app_config.call_data[call_id];
02530             pjsip_endpoint *endpt = pjsua_get_pjsip_endpt();
02531 
02532             cd->timer.id = PJSUA_INVALID_ID;
02533             pjsip_endpt_cancel_timer(endpt, &cd->timer);
02534         }
02535 
02536         /* Rewind play file when hangup automatically, 
02537          * since file is not looped
02538          */
02539         if (app_config.auto_play_hangup)
02540             pjsua_player_set_pos(app_config.wav_id, 0);
02541 
02542 
02543         PJ_LOG(3,(THIS_FILE, "Call %d is DISCONNECTED [reason=%d (%s)]", 
02544                   call_id,
02545                   call_info.last_status,
02546                   call_info.last_status_text.ptr));
02547 
02548         if (call_id == current_call) {
02549             find_next_call();
02550         }
02551 
02552         /* Dump media state upon disconnected */
02553         if (1) {
02554             PJ_LOG(5,(THIS_FILE, 
02555                       "Call %d disconnected, dumping media stats..", 
02556                       call_id));
02557             log_call_dump(call_id);
02558         }
02559 
02560     } else {
02561 
02562         if (app_config.duration!=NO_LIMIT && 
02563             call_info.state == PJSIP_INV_STATE_CONFIRMED) 
02564         {
02565             /* Schedule timer to hangup call after the specified duration */
02566             struct call_data *cd = &app_config.call_data[call_id];
02567             pjsip_endpoint *endpt = pjsua_get_pjsip_endpt();
02568             pj_time_val delay;
02569 
02570             cd->timer.id = call_id;
02571             delay.sec = app_config.duration;
02572             delay.msec = 0;
02573             pjsip_endpt_schedule_timer(endpt, &cd->timer, &delay);
02574         }
02575 
02576         if (call_info.state == PJSIP_INV_STATE_EARLY) {
02577             int code;
02578             pj_str_t reason;
02579             pjsip_msg *msg;
02580 
02581             /* This can only occur because of TX or RX message */
02582             pj_assert(e->type == PJSIP_EVENT_TSX_STATE);
02583 
02584             if (e->body.tsx_state.type == PJSIP_EVENT_RX_MSG) {
02585                 msg = e->body.tsx_state.src.rdata->msg_info.msg;
02586             } else {
02587                 msg = e->body.tsx_state.src.tdata->msg;
02588             }
02589 
02590             code = msg->line.status.code;
02591             reason = msg->line.status.reason;
02592 
02593             /* Start ringback for 180 for UAC unless there's SDP in 180 */
02594             if (call_info.role==PJSIP_ROLE_UAC && code==180 && 
02595                 msg->body == NULL && 
02596                 call_info.media_status==PJSUA_CALL_MEDIA_NONE) 
02597             {
02598                 ringback_start(call_id);
02599             }
02600 
02601             PJ_LOG(3,(THIS_FILE, "Call %d state changed to %s (%d %.*s)", 
02602                       call_id, call_info.state_text.ptr,
02603                       code, (int)reason.slen, reason.ptr));
02604         } else {
02605             PJ_LOG(3,(THIS_FILE, "Call %d state changed to %s", 
02606                       call_id,
02607                       call_info.state_text.ptr));
02608         }
02609 
02610         if (current_call==PJSUA_INVALID_ID)
02611             current_call = call_id;
02612 
02613     }
02614 }
02615 
02616 
02620 static void on_incoming_call(pjsua_acc_id acc_id, pjsua_call_id call_id,
02621                              pjsip_rx_data *rdata)
02622 {
02623     pjsua_call_info call_info;
02624 
02625     PJ_UNUSED_ARG(acc_id);
02626     PJ_UNUSED_ARG(rdata);
02627 
02628     pjsua_call_get_info(call_id, &call_info);
02629 
02630     if (current_call==PJSUA_INVALID_ID)
02631         current_call = call_id;
02632 
02633 #ifdef USE_GUI
02634     if (!showNotification(call_id))
02635         return;
02636 #endif
02637 
02638     /* Start ringback */
02639     ring_start(call_id);
02640     
02641     if (app_config.auto_answer > 0) {
02642         pjsua_call_setting call_opt;
02643 
02644         pjsua_call_setting_default(&call_opt);
02645         call_opt.aud_cnt = app_config.aud_cnt;
02646         call_opt.vid_cnt = app_config.vid.vid_cnt;
02647 
02648         pjsua_call_answer2(call_id, &call_opt, app_config.auto_answer, NULL, NULL);
02649     }
02650     
02651     if (app_config.auto_answer < 200) {
02652         char notif_st[80] = {0};
02653 
02654 #if PJSUA_HAS_VIDEO
02655         if (call_info.rem_offerer && call_info.rem_vid_cnt) {
02656             snprintf(notif_st, sizeof(notif_st), 
02657                      "To %s the video, type \"vid %s\" first, "
02658                      "before answering the call!\n",
02659                      (app_config.vid.vid_cnt? "reject":"accept"),
02660                      (app_config.vid.vid_cnt? "disable":"enable"));
02661         }
02662 #endif
02663 
02664         PJ_LOG(3,(THIS_FILE,
02665                   "Incoming call for account %d!\n"
02666                   "Media count: %d audio & %d video\n"
02667                   "%s"
02668                   "From: %s\n"
02669                   "To: %s\n"
02670                   "Press a to answer or h to reject call",
02671                   acc_id,
02672                   call_info.rem_aud_cnt,
02673                   call_info.rem_vid_cnt,
02674                   notif_st,
02675                   call_info.remote_info.ptr,
02676                   call_info.local_info.ptr));
02677     }
02678 }
02679 
02680 
02681 /*
02682  * Handler when a transaction within a call has changed state.
02683  */
02684 static void on_call_tsx_state(pjsua_call_id call_id,
02685                               pjsip_transaction *tsx,
02686                               pjsip_event *e)
02687 {
02688     const pjsip_method info_method = 
02689     {
02690         PJSIP_OTHER_METHOD,
02691         { "INFO", 4 }
02692     };
02693 
02694     if (pjsip_method_cmp(&tsx->method, &info_method)==0) {
02695         /*
02696          * Handle INFO method.
02697          */
02698         const pj_str_t STR_APPLICATION = { "application", 11};
02699         const pj_str_t STR_DTMF_RELAY  = { "dtmf-relay", 10 };
02700         pjsip_msg_body *body = NULL;
02701         pj_bool_t dtmf_info = PJ_FALSE;
02702         
02703         if (tsx->role == PJSIP_ROLE_UAC) {
02704             if (e->body.tsx_state.type == PJSIP_EVENT_TX_MSG)
02705                 body = e->body.tsx_state.src.tdata->msg->body;
02706             else
02707                 body = e->body.tsx_state.tsx->last_tx->msg->body;
02708         } else {
02709             if (e->body.tsx_state.type == PJSIP_EVENT_RX_MSG)
02710                 body = e->body.tsx_state.src.rdata->msg_info.msg->body;
02711         }
02712         
02713         /* Check DTMF content in the INFO message */
02714         if (body && body->len &&
02715             pj_stricmp(&body->content_type.type, &STR_APPLICATION)==0 &&
02716             pj_stricmp(&body->content_type.subtype, &STR_DTMF_RELAY)==0)
02717         {
02718             dtmf_info = PJ_TRUE;
02719         }
02720 
02721         if (dtmf_info && tsx->role == PJSIP_ROLE_UAC && 
02722             (tsx->state == PJSIP_TSX_STATE_COMPLETED ||
02723                (tsx->state == PJSIP_TSX_STATE_TERMINATED &&
02724                 e->body.tsx_state.prev_state != PJSIP_TSX_STATE_COMPLETED))) 
02725         {
02726             /* Status of outgoing INFO request */
02727             if (tsx->status_code >= 200 && tsx->status_code < 300) {
02728                 PJ_LOG(4,(THIS_FILE, 
02729                           "Call %d: DTMF sent successfully with INFO",
02730                           call_id));
02731             } else if (tsx->status_code >= 300) {
02732                 PJ_LOG(4,(THIS_FILE, 
02733                           "Call %d: Failed to send DTMF with INFO: %d/%.*s",
02734                           call_id,
02735                           tsx->status_code,
02736                           (int)tsx->status_text.slen,
02737                           tsx->status_text.ptr));
02738             }
02739         } else if (dtmf_info && tsx->role == PJSIP_ROLE_UAS &&
02740                    tsx->state == PJSIP_TSX_STATE_TRYING)
02741         {
02742             /* Answer incoming INFO with 200/OK */
02743             pjsip_rx_data *rdata;
02744             pjsip_tx_data *tdata;
02745             pj_status_t status;
02746 
02747             rdata = e->body.tsx_state.src.rdata;
02748 
02749             if (rdata->msg_info.msg->body) {
02750                 status = pjsip_endpt_create_response(tsx->endpt, rdata,
02751                                                      200, NULL, &tdata);
02752                 if (status == PJ_SUCCESS)
02753                     status = pjsip_tsx_send_msg(tsx, tdata);
02754 
02755                 PJ_LOG(3,(THIS_FILE, "Call %d: incoming INFO:\n%.*s", 
02756                           call_id,
02757                           (int)rdata->msg_info.msg->body->len,
02758                           rdata->msg_info.msg->body->data));
02759             } else {
02760                 status = pjsip_endpt_create_response(tsx->endpt, rdata,
02761                                                      400, NULL, &tdata);
02762                 if (status == PJ_SUCCESS)
02763                     status = pjsip_tsx_send_msg(tsx, tdata);
02764             }
02765         }
02766     }
02767 }
02768 
02769 /* General processing for media state. "mi" is the media index */
02770 static void on_call_generic_media_state(pjsua_call_info *ci, unsigned mi,
02771                                         pj_bool_t *has_error)
02772 {
02773     const char *status_name[] = {
02774         "None",
02775         "Active",
02776         "Local hold",
02777         "Remote hold",
02778         "Error"
02779     };
02780 
02781     PJ_UNUSED_ARG(has_error);
02782 
02783     pj_assert(ci->media[mi].status <= PJ_ARRAY_SIZE(status_name));
02784     pj_assert(PJSUA_CALL_MEDIA_ERROR == 4);
02785 
02786     PJ_LOG(4,(THIS_FILE, "Call %d media %d [type=%s], status is %s",
02787               ci->id, mi, pjmedia_type_name(ci->media[mi].type),
02788               status_name[ci->media[mi].status]));
02789 }
02790 
02791 /* Process audio media state. "mi" is the media index. */
02792 static void on_call_audio_state(pjsua_call_info *ci, unsigned mi,
02793                                 pj_bool_t *has_error)
02794 {
02795     PJ_UNUSED_ARG(has_error);
02796 
02797     /* Stop ringback */
02798     ring_stop(ci->id);
02799 
02800     /* Connect ports appropriately when media status is ACTIVE or REMOTE HOLD,
02801      * otherwise we should NOT connect the ports.
02802      */
02803     if (ci->media[mi].status == PJSUA_CALL_MEDIA_ACTIVE ||
02804         ci->media[mi].status == PJSUA_CALL_MEDIA_REMOTE_HOLD)
02805     {
02806         pj_bool_t connect_sound = PJ_TRUE;
02807         pj_bool_t disconnect_mic = PJ_FALSE;
02808         pjsua_conf_port_id call_conf_slot;
02809 
02810         call_conf_slot = ci->media[mi].stream.aud.conf_slot;
02811 
02812         /* Loopback sound, if desired */
02813         if (app_config.auto_loop) {
02814             pjsua_conf_connect(call_conf_slot, call_conf_slot);
02815             connect_sound = PJ_FALSE;
02816         }
02817 
02818         /* Automatically record conversation, if desired */
02819         if (app_config.auto_rec && app_config.rec_port != PJSUA_INVALID_ID) {
02820             pjsua_conf_connect(call_conf_slot, app_config.rec_port);
02821         }
02822 
02823         /* Stream a file, if desired */
02824         if ((app_config.auto_play || app_config.auto_play_hangup) && 
02825             app_config.wav_port != PJSUA_INVALID_ID)
02826         {
02827             pjsua_conf_connect(app_config.wav_port, call_conf_slot);
02828             connect_sound = PJ_FALSE;
02829         }
02830 
02831         /* Stream AVI, if desired */
02832         if (app_config.avi_auto_play &&
02833             app_config.avi_def_idx != PJSUA_INVALID_ID &&
02834             app_config.avi[app_config.avi_def_idx].slot != PJSUA_INVALID_ID)
02835         {
02836             pjsua_conf_connect(app_config.avi[app_config.avi_def_idx].slot,
02837                                call_conf_slot);
02838             disconnect_mic = PJ_TRUE;
02839         }
02840 
02841         /* Put call in conference with other calls, if desired */
02842         if (app_config.auto_conf) {
02843             pjsua_call_id call_ids[PJSUA_MAX_CALLS];
02844             unsigned call_cnt=PJ_ARRAY_SIZE(call_ids);
02845             unsigned i;
02846 
02847             /* Get all calls, and establish media connection between
02848              * this call and other calls.
02849              */
02850             pjsua_enum_calls(call_ids, &call_cnt);
02851 
02852             for (i=0; i<call_cnt; ++i) {
02853                 if (call_ids[i] == ci->id)
02854                     continue;
02855                 
02856                 if (!pjsua_call_has_media(call_ids[i]))
02857                     continue;
02858 
02859                 pjsua_conf_connect(call_conf_slot,
02860                                    pjsua_call_get_conf_port(call_ids[i]));
02861                 pjsua_conf_connect(pjsua_call_get_conf_port(call_ids[i]),
02862                                    call_conf_slot);
02863 
02864                 /* Automatically record conversation, if desired */
02865                 if (app_config.auto_rec && app_config.rec_port != PJSUA_INVALID_ID) {
02866                     pjsua_conf_connect(pjsua_call_get_conf_port(call_ids[i]), 
02867                                        app_config.rec_port);
02868                 }
02869 
02870             }
02871 
02872             /* Also connect call to local sound device */
02873             connect_sound = PJ_TRUE;
02874         }
02875 
02876         /* Otherwise connect to sound device */
02877         if (connect_sound) {
02878             pjsua_conf_connect(call_conf_slot, 0);
02879             if (!disconnect_mic)
02880                 pjsua_conf_connect(0, call_conf_slot);
02881 
02882             /* Automatically record conversation, if desired */
02883             if (app_config.auto_rec && app_config.rec_port != PJSUA_INVALID_ID) {
02884                 pjsua_conf_connect(call_conf_slot, app_config.rec_port);
02885                 pjsua_conf_connect(0, app_config.rec_port);
02886             }
02887         }
02888     }
02889 }
02890 
02891 /* arrange windows. arg:
02892  *   -1:    arrange all windows
02893  *   != -1: arrange only this window id
02894  */
02895 static void arrange_window(pjsua_vid_win_id wid)
02896 {
02897 #if PJSUA_HAS_VIDEO
02898     pjmedia_coord pos;
02899     int i, last;
02900 
02901     pos.x = 0;
02902     pos.y = 10;
02903     last = (wid == PJSUA_INVALID_ID) ? PJSUA_MAX_VID_WINS : wid;
02904 
02905     for (i=0; i<last; ++i) {
02906         pjsua_vid_win_info wi;
02907         pj_status_t status;
02908 
02909         status = pjsua_vid_win_get_info(i, &wi);
02910         if (status != PJ_SUCCESS)
02911             continue;
02912 
02913         if (wid == PJSUA_INVALID_ID)
02914             pjsua_vid_win_set_pos(i, &pos);
02915 
02916         if (wi.show)
02917             pos.y += wi.size.h;
02918     }
02919 
02920     if (wid != PJSUA_INVALID_ID)
02921         pjsua_vid_win_set_pos(wid, &pos);
02922 #else
02923     PJ_UNUSED_ARG(wid);
02924 #endif
02925 }
02926 
02927 /* Process video media state. "mi" is the media index. */
02928 static void on_call_video_state(pjsua_call_info *ci, unsigned mi,
02929                                 pj_bool_t *has_error)
02930 {
02931     if (ci->media_status != PJSUA_CALL_MEDIA_ACTIVE)
02932         return;
02933 
02934     arrange_window(ci->media[mi].stream.vid.win_in);
02935 
02936     PJ_UNUSED_ARG(has_error);
02937 }
02938 
02939 /*
02940  * Callback on media state changed event.
02941  * The action may connect the call to sound device, to file, or
02942  * to loop the call.
02943  */
02944 static void on_call_media_state(pjsua_call_id call_id)
02945 {
02946     pjsua_call_info call_info;
02947     unsigned mi;
02948     pj_bool_t has_error = PJ_FALSE;
02949 
02950     pjsua_call_get_info(call_id, &call_info);
02951 
02952     for (mi=0; mi<call_info.media_cnt; ++mi) {
02953         on_call_generic_media_state(&call_info, mi, &has_error);
02954 
02955         switch (call_info.media[mi].type) {
02956         case PJMEDIA_TYPE_AUDIO:
02957             on_call_audio_state(&call_info, mi, &has_error);
02958             break;
02959         case PJMEDIA_TYPE_VIDEO:
02960             on_call_video_state(&call_info, mi, &has_error);
02961             break;
02962         default:
02963             /* Make gcc happy about enum not handled by switch/case */
02964             break;
02965         }
02966     }
02967 
02968     if (has_error) {
02969         pj_str_t reason = pj_str("Media failed");
02970         pjsua_call_hangup(call_id, 500, &reason, NULL);
02971     }
02972 
02973 #if PJSUA_HAS_VIDEO
02974     /* Check if remote has just tried to enable video */
02975     if (call_info.rem_offerer && call_info.rem_vid_cnt)
02976     {
02977         int vid_idx;
02978 
02979         /* Check if there is active video */
02980         vid_idx = pjsua_call_get_vid_stream_idx(call_id);
02981         if (vid_idx == -1 || call_info.media[vid_idx].dir == PJMEDIA_DIR_NONE) {
02982             PJ_LOG(3,(THIS_FILE,
02983                       "Just rejected incoming video offer on call %d, "
02984                       "use \"vid call enable %d\" or \"vid call add\" to enable video!",
02985                       call_id, vid_idx));
02986         }
02987     }
02988 #endif
02989 }
02990 
02991 /*
02992  * DTMF callback.
02993  */
02994 static void call_on_dtmf_callback(pjsua_call_id call_id, int dtmf)
02995 {
02996     PJ_LOG(3,(THIS_FILE, "Incoming DTMF on call %d: %c", call_id, dtmf));
02997 }
02998 
02999 /*
03000  * Redirection handler.
03001  */
03002 static pjsip_redirect_op call_on_redirected(pjsua_call_id call_id, 
03003                                             const pjsip_uri *target,
03004                                             const pjsip_event *e)
03005 {
03006     PJ_UNUSED_ARG(e);
03007 
03008     if (app_config.redir_op == PJSIP_REDIRECT_PENDING) {
03009         char uristr[PJSIP_MAX_URL_SIZE];
03010         int len;
03011 
03012         len = pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, target, uristr, 
03013                               sizeof(uristr));
03014         if (len < 1) {
03015             pj_ansi_strcpy(uristr, "--URI too long--");
03016         }
03017 
03018         PJ_LOG(3,(THIS_FILE, "Call %d is being redirected to %.*s. "
03019                   "Press 'Ra' to accept, 'Rr' to reject, or 'Rd' to "
03020                   "disconnect.",
03021                   call_id, len, uristr));
03022     }
03023 
03024     return app_config.redir_op;
03025 }
03026 
03027 /*
03028  * Handler registration status has changed.
03029  */
03030 static void on_reg_state(pjsua_acc_id acc_id)
03031 {
03032     PJ_UNUSED_ARG(acc_id);
03033 
03034     // Log already written.
03035 }
03036 
03037 
03038 /*
03039  * Handler for incoming presence subscription request
03040  */
03041 static void on_incoming_subscribe(pjsua_acc_id acc_id,
03042                                   pjsua_srv_pres *srv_pres,
03043                                   pjsua_buddy_id buddy_id,
03044                                   const pj_str_t *from,
03045                                   pjsip_rx_data *rdata,
03046                                   pjsip_status_code *code,
03047                                   pj_str_t *reason,
03048                                   pjsua_msg_data *msg_data)
03049 {
03050     /* Just accept the request (the default behavior) */
03051     PJ_UNUSED_ARG(acc_id);
03052     PJ_UNUSED_ARG(srv_pres);
03053     PJ_UNUSED_ARG(buddy_id);
03054     PJ_UNUSED_ARG(from);
03055     PJ_UNUSED_ARG(rdata);
03056     PJ_UNUSED_ARG(code);
03057     PJ_UNUSED_ARG(reason);
03058     PJ_UNUSED_ARG(msg_data);
03059 }
03060 
03061 
03062 /*
03063  * Handler on buddy state changed.
03064  */
03065 static void on_buddy_state(pjsua_buddy_id buddy_id)
03066 {
03067     pjsua_buddy_info info;
03068     pjsua_buddy_get_info(buddy_id, &info);
03069 
03070     PJ_LOG(3,(THIS_FILE, "%.*s status is %.*s, subscription state is %s "
03071                          "(last termination reason code=%d %.*s)",
03072               (int)info.uri.slen,
03073               info.uri.ptr,
03074               (int)info.status_text.slen,
03075               info.status_text.ptr,
03076               info.sub_state_name,
03077               info.sub_term_code,
03078               (int)info.sub_term_reason.slen,
03079               info.sub_term_reason.ptr));
03080 }
03081 
03082 
03083 /*
03084  * Subscription state has changed.
03085  */
03086 static void on_buddy_evsub_state(pjsua_buddy_id buddy_id,
03087                                  pjsip_evsub *sub,
03088                                  pjsip_event *event)
03089 {
03090     char event_info[80];
03091 
03092     PJ_UNUSED_ARG(sub);
03093 
03094     event_info[0] = '\0';
03095 
03096     if (event->type == PJSIP_EVENT_TSX_STATE &&
03097             event->body.tsx_state.type == PJSIP_EVENT_RX_MSG)
03098     {
03099         pjsip_rx_data *rdata = event->body.tsx_state.src.rdata;
03100         snprintf(event_info, sizeof(event_info),
03101                  " (RX %s)",
03102                  pjsip_rx_data_get_info(rdata));
03103     }
03104 
03105     PJ_LOG(4,(THIS_FILE,
03106               "Buddy %d: subscription state: %s (event: %s%s)",
03107               buddy_id, pjsip_evsub_get_state_name(sub),
03108               pjsip_event_str(event->type),
03109               event_info));
03110 
03111 }
03112 
03113 
03117 static void on_pager(pjsua_call_id call_id, const pj_str_t *from, 
03118                      const pj_str_t *to, const pj_str_t *contact,
03119                      const pj_str_t *mime_type, const pj_str_t *text)
03120 {
03121     /* Note: call index may be -1 */
03122     PJ_UNUSED_ARG(call_id);
03123     PJ_UNUSED_ARG(to);
03124     PJ_UNUSED_ARG(contact);
03125     PJ_UNUSED_ARG(mime_type);
03126 
03127     PJ_LOG(3,(THIS_FILE,"MESSAGE from %.*s: %.*s (%.*s)",
03128               (int)from->slen, from->ptr,
03129               (int)text->slen, text->ptr,
03130               (int)mime_type->slen, mime_type->ptr));
03131 }
03132 
03133 
03137 static void on_typing(pjsua_call_id call_id, const pj_str_t *from,
03138                       const pj_str_t *to, const pj_str_t *contact,
03139                       pj_bool_t is_typing)
03140 {
03141     PJ_UNUSED_ARG(call_id);
03142     PJ_UNUSED_ARG(to);
03143     PJ_UNUSED_ARG(contact);
03144 
03145     PJ_LOG(3,(THIS_FILE, "IM indication: %.*s %s",
03146               (int)from->slen, from->ptr,
03147               (is_typing?"is typing..":"has stopped typing")));
03148 }
03149 
03150 
03154 static void on_call_transfer_status(pjsua_call_id call_id,
03155                                     int status_code,
03156                                     const pj_str_t *status_text,
03157                                     pj_bool_t final,
03158                                     pj_bool_t *p_cont)
03159 {
03160     PJ_LOG(3,(THIS_FILE, "Call %d: transfer status=%d (%.*s) %s",
03161               call_id, status_code,
03162               (int)status_text->slen, status_text->ptr,
03163               (final ? "[final]" : "")));
03164 
03165     if (status_code/100 == 2) {
03166         PJ_LOG(3,(THIS_FILE, 
03167                   "Call %d: call transfered successfully, disconnecting call",
03168                   call_id));
03169         pjsua_call_hangup(call_id, PJSIP_SC_GONE, NULL, NULL);
03170         *p_cont = PJ_FALSE;
03171     }
03172 }
03173 
03174 
03175 /*
03176  * Notification that call is being replaced.
03177  */
03178 static void on_call_replaced(pjsua_call_id old_call_id,
03179                              pjsua_call_id new_call_id)
03180 {
03181     pjsua_call_info old_ci, new_ci;
03182 
03183     pjsua_call_get_info(old_call_id, &old_ci);
03184     pjsua_call_get_info(new_call_id, &new_ci);
03185 
03186     PJ_LOG(3,(THIS_FILE, "Call %d with %.*s is being replaced by "
03187                          "call %d with %.*s",
03188                          old_call_id, 
03189                          (int)old_ci.remote_info.slen, old_ci.remote_info.ptr,
03190                          new_call_id,
03191                          (int)new_ci.remote_info.slen, new_ci.remote_info.ptr));
03192 }
03193 
03194 
03195 /*
03196  * NAT type detection callback.
03197  */
03198 static void on_nat_detect(const pj_stun_nat_detect_result *res)
03199 {
03200     if (res->status != PJ_SUCCESS) {
03201         pjsua_perror(THIS_FILE, "NAT detection failed", res->status);
03202     } else {
03203         PJ_LOG(3, (THIS_FILE, "NAT detected as %s", res->nat_type_name));
03204     }
03205 }
03206 
03207 
03208 /*
03209  * MWI indication
03210  */
03211 static void on_mwi_info(pjsua_acc_id acc_id, pjsua_mwi_info *mwi_info)
03212 {
03213     pj_str_t body;
03214     
03215     PJ_LOG(3,(THIS_FILE, "Received MWI for acc %d:", acc_id));
03216 
03217     if (mwi_info->rdata->msg_info.ctype) {
03218         const pjsip_ctype_hdr *ctype = mwi_info->rdata->msg_info.ctype;
03219 
03220         PJ_LOG(3,(THIS_FILE, " Content-Type: %.*s/%.*s",
03221                   (int)ctype->media.type.slen,
03222                   ctype->media.type.ptr,
03223                   (int)ctype->media.subtype.slen,
03224                   ctype->media.subtype.ptr));
03225     }
03226 
03227     if (!mwi_info->rdata->msg_info.msg->body) {
03228         PJ_LOG(3,(THIS_FILE, "  no message body"));
03229         return;
03230     }
03231 
03232     body.ptr = mwi_info->rdata->msg_info.msg->body->data;
03233     body.slen = mwi_info->rdata->msg_info.msg->body->len;
03234 
03235     PJ_LOG(3,(THIS_FILE, " Body:\n%.*s", (int)body.slen, body.ptr));
03236 }
03237 
03238 
03239 /*
03240  * Transport status notification
03241  */
03242 static void on_transport_state(pjsip_transport *tp, 
03243                                pjsip_transport_state state,
03244                                const pjsip_transport_state_info *info)
03245 {
03246     char host_port[128];
03247 
03248     pj_ansi_snprintf(host_port, sizeof(host_port), "[%.*s:%d]",
03249                      (int)tp->remote_name.host.slen,
03250                      tp->remote_name.host.ptr,
03251                      tp->remote_name.port);
03252 
03253     switch (state) {
03254     case PJSIP_TP_STATE_CONNECTED:
03255         {
03256             PJ_LOG(3,(THIS_FILE, "SIP %s transport is connected to %s",
03257                      tp->type_name, host_port));
03258         }
03259         break;
03260 
03261     case PJSIP_TP_STATE_DISCONNECTED:
03262         {
03263             char buf[100];
03264 
03265             snprintf(buf, sizeof(buf), "SIP %s transport is disconnected from %s",
03266                      tp->type_name, host_port);
03267             pjsua_perror(THIS_FILE, buf, info->status);
03268         }
03269         break;
03270 
03271     default:
03272         break;
03273     }
03274 
03275 #if defined(PJSIP_HAS_TLS_TRANSPORT) && PJSIP_HAS_TLS_TRANSPORT!=0
03276 
03277     if (!pj_ansi_stricmp(tp->type_name, "tls") && info->ext_info &&
03278         (state == PJSIP_TP_STATE_CONNECTED || 
03279          ((pjsip_tls_state_info*)info->ext_info)->
03280                                  ssl_sock_info->verify_status != PJ_SUCCESS))
03281     {
03282         pjsip_tls_state_info *tls_info = (pjsip_tls_state_info*)info->ext_info;
03283         pj_ssl_sock_info *ssl_sock_info = tls_info->ssl_sock_info;
03284         char buf[2048];
03285         const char *verif_msgs[32];
03286         unsigned verif_msg_cnt;
03287 
03288         /* Dump server TLS cipher */
03289         PJ_LOG(4,(THIS_FILE, "TLS cipher used: 0x%06X/%s",
03290                   ssl_sock_info->cipher,
03291                   pj_ssl_cipher_name(ssl_sock_info->cipher) ));
03292 
03293         /* Dump server TLS certificate */
03294         pj_ssl_cert_info_dump(ssl_sock_info->remote_cert_info, "  ",
03295                               buf, sizeof(buf));
03296         PJ_LOG(4,(THIS_FILE, "TLS cert info of %s:\n%s", host_port, buf));
03297 
03298         /* Dump server TLS certificate verification result */
03299         verif_msg_cnt = PJ_ARRAY_SIZE(verif_msgs);
03300         pj_ssl_cert_get_verify_status_strings(ssl_sock_info->verify_status,
03301                                               verif_msgs, &verif_msg_cnt);
03302         PJ_LOG(3,(THIS_FILE, "TLS cert verification result of %s : %s",
03303                              host_port,
03304                              (verif_msg_cnt == 1? verif_msgs[0]:"")));
03305         if (verif_msg_cnt > 1) {
03306             unsigned i;
03307             for (i = 0; i < verif_msg_cnt; ++i)
03308                 PJ_LOG(3,(THIS_FILE, "- %s", verif_msgs[i]));
03309         }
03310 
03311         if (ssl_sock_info->verify_status &&
03312             !app_config.udp_cfg.tls_setting.verify_server) 
03313         {
03314             PJ_LOG(3,(THIS_FILE, "PJSUA is configured to ignore TLS cert "
03315                                  "verification errors"));
03316         }
03317     }
03318 
03319 #endif
03320 
03321 }
03322 
03323 /*
03324  * Notification on ICE error.
03325  */
03326 static void on_ice_transport_error(int index, pj_ice_strans_op op,
03327                                    pj_status_t status, void *param)
03328 {
03329     PJ_UNUSED_ARG(op);
03330     PJ_UNUSED_ARG(param);
03331     PJ_PERROR(1,(THIS_FILE, status,
03332                  "ICE keep alive failure for transport %d", index));
03333 }
03334 
03335 /*
03336  * Notification on sound device operation.
03337  */
03338 static pj_status_t on_snd_dev_operation(int operation)
03339 {
03340     PJ_LOG(3,(THIS_FILE, "Turning sound device %s", (operation? "ON":"OFF")));
03341     return PJ_SUCCESS;
03342 }
03343 
03344 /* Callback on media events */
03345 static void on_call_media_event(pjsua_call_id call_id,
03346                                 unsigned med_idx,
03347                                 pjmedia_event *event)
03348 {
03349     char event_name[5];
03350 
03351     PJ_LOG(5,(THIS_FILE, "Event %s",
03352               pjmedia_fourcc_name(event->type, event_name)));
03353 
03354 #if PJSUA_HAS_VIDEO
03355     if (event->type == PJMEDIA_EVENT_FMT_CHANGED) {
03356         /* Adjust renderer window size to original video size */
03357         pjsua_call_info ci;
03358         pjsua_vid_win_id wid;
03359         pjmedia_rect_size size;
03360 
03361         pjsua_call_get_info(call_id, &ci);
03362 
03363         if ((ci.media[med_idx].type == PJMEDIA_TYPE_VIDEO) &&
03364             (ci.media[med_idx].dir & PJMEDIA_DIR_DECODING))
03365         {
03366             wid = ci.media[med_idx].stream.vid.win_in;
03367             size = event->data.fmt_changed.new_fmt.det.vid.size;
03368             pjsua_vid_win_set_size(wid, &size);
03369         }
03370 
03371         /* Re-arrange video windows */
03372         arrange_window(PJSUA_INVALID_ID);
03373     }
03374 #else
03375     PJ_UNUSED_ARG(call_id);
03376     PJ_UNUSED_ARG(med_idx);
03377     PJ_UNUSED_ARG(event);
03378 #endif
03379 }
03380 
03381 #ifdef TRANSPORT_ADAPTER_SAMPLE
03382 /*
03383  * This callback is called when media transport needs to be created.
03384  */
03385 static pjmedia_transport* on_create_media_transport(pjsua_call_id call_id,
03386                                                     unsigned media_idx,
03387                                                     pjmedia_transport *base_tp,
03388                                                     unsigned flags)
03389 {
03390     pjmedia_transport *adapter;
03391     pj_status_t status;
03392 
03393     /* Create the adapter */
03394     status = pjmedia_tp_adapter_create(pjsua_get_pjmedia_endpt(),
03395                                        NULL, base_tp,
03396                                        (flags & PJSUA_MED_TP_CLOSE_MEMBER),
03397                                        &adapter);
03398     if (status != PJ_SUCCESS) {
03399         PJ_PERROR(1,(THIS_FILE, status, "Error creating adapter"));
03400         return NULL;
03401     }
03402 
03403     PJ_LOG(3,(THIS_FILE, "Media transport is created for call %d media %d",
03404               call_id, media_idx));
03405 
03406     return adapter;
03407 }
03408 #endif
03409 
03410 /*
03411  * Print buddy list.
03412  */
03413 static void print_buddy_list(void)
03414 {
03415     pjsua_buddy_id ids[64];
03416     int i;
03417     unsigned count = PJ_ARRAY_SIZE(ids);
03418 
03419     puts("Buddy list:");
03420 
03421     pjsua_enum_buddies(ids, &count);
03422 
03423     if (count == 0)
03424         puts(" -none-");
03425     else {
03426         for (i=0; i<(int)count; ++i) {
03427             pjsua_buddy_info info;
03428 
03429             if (pjsua_buddy_get_info(ids[i], &info) != PJ_SUCCESS)
03430                 continue;
03431 
03432             printf(" [%2d] <%.*s>  %.*s\n", 
03433                     ids[i]+1, 
03434                     (int)info.status_text.slen,
03435                     info.status_text.ptr, 
03436                     (int)info.uri.slen,
03437                     info.uri.ptr);
03438         }
03439     }
03440     puts("");
03441 }
03442 
03443 
03444 /*
03445  * Print account status.
03446  */
03447 static void print_acc_status(int acc_id)
03448 {
03449     char buf[80];
03450     pjsua_acc_info info;
03451 
03452     pjsua_acc_get_info(acc_id, &info);
03453 
03454     if (!info.has_registration) {
03455         pj_ansi_snprintf(buf, sizeof(buf), "%.*s", 
03456                          (int)info.status_text.slen,
03457                          info.status_text.ptr);
03458 
03459     } else {
03460         pj_ansi_snprintf(buf, sizeof(buf),
03461                          "%d/%.*s (expires=%d)",
03462                          info.status,
03463                          (int)info.status_text.slen,
03464                          info.status_text.ptr,
03465                          info.expires);
03466 
03467     }
03468 
03469     printf(" %c[%2d] %.*s: %s\n", (acc_id==current_acc?'*':' '),
03470            acc_id,  (int)info.acc_uri.slen, info.acc_uri.ptr, buf);
03471     printf("       Online status: %.*s\n", 
03472         (int)info.online_status_text.slen,
03473         info.online_status_text.ptr);
03474 }
03475 
03476 /* Playfile done notification, set timer to hangup calls */
03477 pj_status_t on_playfile_done(pjmedia_port *port, void *usr_data)
03478 {
03479     pj_time_val delay;
03480 
03481     PJ_UNUSED_ARG(port);
03482     PJ_UNUSED_ARG(usr_data);
03483 
03484     /* Just rewind WAV when it is played outside of call */
03485     if (pjsua_call_get_count() == 0) {
03486         pjsua_player_set_pos(app_config.wav_id, 0);
03487         return PJ_SUCCESS;
03488     }
03489 
03490     /* Timer is already active */
03491     if (app_config.auto_hangup_timer.id == 1)
03492         return PJ_SUCCESS;
03493 
03494     app_config.auto_hangup_timer.id = 1;
03495     delay.sec = 0;
03496     delay.msec = 200; /* Give 200 ms before hangup */
03497     pjsip_endpt_schedule_timer(pjsua_get_pjsip_endpt(), 
03498                                &app_config.auto_hangup_timer, 
03499                                &delay);
03500 
03501     return PJ_SUCCESS;
03502 }
03503 
03504 /* Auto hangup timer callback */
03505 static void hangup_timeout_callback(pj_timer_heap_t *timer_heap,
03506                                     struct pj_timer_entry *entry)
03507 {
03508     PJ_UNUSED_ARG(timer_heap);
03509     PJ_UNUSED_ARG(entry);
03510 
03511     app_config.auto_hangup_timer.id = 0;
03512     pjsua_call_hangup_all();
03513 }
03514 
03515 /*
03516  * Show a bit of help.
03517  */
03518 static void keystroke_help(void)
03519 {
03520     pjsua_acc_id acc_ids[16];
03521     unsigned count = PJ_ARRAY_SIZE(acc_ids);
03522     int i;
03523 
03524     printf(">>>>\n");
03525 
03526     pjsua_enum_accs(acc_ids, &count);
03527 
03528     printf("Account list:\n");
03529     for (i=0; i<(int)count; ++i)
03530         print_acc_status(acc_ids[i]);
03531 
03532     print_buddy_list();
03533     
03534     //puts("Commands:");
03535     puts("+=============================================================================+");
03536     puts("|       Call Commands:         |   Buddy, IM & Presence:  |     Account:      |");
03537     puts("|                              |                          |                   |");
03538     puts("|  m  Make new call            | +b  Add new buddy       .| +a  Add new accnt |");
03539     puts("|  M  Make multiple calls      | -b  Delete buddy         | -a  Delete accnt. |");
03540     puts("|  a  Answer call              |  i  Send IM              | !a  Modify accnt. |");
03541     puts("|  h  Hangup call  (ha=all)    |  s  Subscribe presence   | rr  (Re-)register |");
03542     puts("|  H  Hold call                |  u  Unsubscribe presence | ru  Unregister    |");
03543     puts("|  v  re-inVite (release hold) |  t  ToGgle Online status |  >  Cycle next ac.|");
03544     puts("|  U  send UPDATE              |  T  Set online status    |  <  Cycle prev ac.|");
03545     puts("| ],[ Select next/prev call    +--------------------------+-------------------+");
03546     puts("|  x  Xfer call                |      Media Commands:     |  Status & Config: |");
03547     puts("|  X  Xfer with Replaces       |                          |                   |");
03548     puts("|  #  Send RFC 2833 DTMF       | cl  List ports           |  d  Dump status   |");
03549     puts("|  *  Send DTMF with INFO      | cc  Connect port         | dd  Dump detailed |");
03550     puts("| dq  Dump curr. call quality  | cd  Disconnect port      | dc  Dump config   |");
03551     puts("|                              |  V  Adjust audio Volume  |  f  Save config   |");
03552     puts("|  S  Send arbitrary REQUEST   | Cp  Codec priorities     |                   |");
03553     puts("+-----------------------------------------------------------------------------+");
03554 #if PJSUA_HAS_VIDEO
03555     puts("| Video: \"vid help\" for more info                                             |");
03556     puts("+-----------------------------------------------------------------------------+");
03557 #endif
03558     puts("|  q  QUIT   L  ReLoad   sleep MS   echo [0|1|txt]     n: detect NAT type     |");
03559     puts("+=============================================================================+");
03560 
03561     i = pjsua_call_get_count();
03562     printf("You have %d active call%s\n", i, (i>1?"s":""));
03563 
03564     if (current_call != PJSUA_INVALID_ID) {
03565         pjsua_call_info ci;
03566         if (pjsua_call_get_info(current_call, &ci)==PJ_SUCCESS)
03567             printf("Current call id=%d to %.*s [%.*s]\n", current_call,
03568                    (int)ci.remote_info.slen, ci.remote_info.ptr,
03569                    (int)ci.state_text.slen, ci.state_text.ptr);
03570     }
03571 }
03572 
03573 /* Help screen for video */
03574 #if PJSUA_HAS_VIDEO
03575 static void vid_show_help(void)
03576 {
03577     pj_bool_t vid_enabled = (app_config.vid.vid_cnt > 0);
03578 
03579     puts("+=============================================================================+");
03580     puts("|                            Video commands:                                  |");
03581     puts("|                                                                             |");
03582     puts("| vid help                  Show this help screen                             |");
03583     puts("| vid enable|disable        Enable or disable video in next offer/answer      |");
03584     puts("| vid acc show              Show current account video settings               |");
03585     puts("| vid acc autorx on|off     Automatically show incoming video on/off          |");
03586     puts("| vid acc autotx on|off     Automatically offer video on/off                  |");
03587     puts("| vid acc cap ID            Set default capture device for current acc        |");
03588     puts("| vid acc rend ID           Set default renderer device for current acc       |");
03589     puts("| vid call rx on|off N      Enable/disable video RX for stream N in curr call |");
03590     puts("| vid call tx on|off N      Enable/disable video TX for stream N in curr call |");
03591     puts("| vid call add              Add video stream for current call                 |");
03592     puts("| vid call enable|disable N Enable/disable stream #N in current call          |");
03593     puts("| vid call cap N ID         Set capture dev ID for stream #N in current call  |");
03594     puts("| vid dev list              List all video devices                            |");
03595     puts("| vid dev refresh           Refresh video device list                         |");
03596     puts("| vid dev prev on|off ID    Enable/disable preview for specified device ID    |");
03597     puts("| vid codec list            List video codecs                                 |");
03598     puts("| vid codec prio ID PRIO    Set codec ID priority to PRIO                     |");
03599     puts("| vid codec fps ID NUM DEN  Set codec ID framerate to (NUM/DEN) fps           |");
03600     puts("| vid codec bw ID AVG MAX   Set codec ID bitrate to AVG & MAX kbps            |");
03601     puts("| vid codec size ID W H     Set codec ID size/resolution to W x H             |");
03602     puts("| vid win list              List all active video windows                     |");
03603     puts("| vid win arrange           Auto arrange windows                              |");
03604     puts("| vid win show|hide ID      Show/hide the specified video window ID           |");
03605     puts("| vid win move ID X Y       Move window ID to position X,Y                    |");
03606     puts("| vid win resize ID w h     Resize window ID to the specified width, height   |");
03607     puts("+=============================================================================+");
03608     printf("| Video will be %s in the next offer/answer %s                            |\n",
03609            (vid_enabled? "enabled" : "disabled"), (vid_enabled? " " : ""));
03610     puts("+=============================================================================+");
03611 }
03612 #endif
03613 
03614 /*
03615  * Input simple string
03616  */
03617 static pj_bool_t simple_input(const char *title, char *buf, pj_size_t len)
03618 {
03619     char *p;
03620 
03621     printf("%s (empty to cancel): ", title); fflush(stdout);
03622     if (fgets(buf, len, stdin) == NULL)
03623         return PJ_FALSE;
03624 
03625     /* Remove trailing newlines. */
03626     for (p=buf; ; ++p) {
03627         if (*p=='\r' || *p=='\n') *p='\0';
03628         else if (!*p) break;
03629     }
03630 
03631     if (!*buf)
03632         return PJ_FALSE;
03633     
03634     return PJ_TRUE;
03635 }
03636 
03637 
03638 #define NO_NB   -2
03639 struct input_result
03640 {
03641     int   nb_result;
03642     char *uri_result;
03643 };
03644 
03645 
03646 /*
03647  * Input URL.
03648  */
03649 static void ui_input_url(const char *title, char *buf, int len, 
03650                          struct input_result *result)
03651 {
03652     result->nb_result = NO_NB;
03653     result->uri_result = NULL;
03654 
03655     print_buddy_list();
03656 
03657     printf("Choices:\n"
03658            "   0         For current dialog.\n"
03659            "  -1         All %d buddies in buddy list\n"
03660            "  [1 -%2d]    Select from buddy list\n"
03661            "  URL        An URL\n"
03662            "  <Enter>    Empty input (or 'q') to cancel\n"
03663            , pjsua_get_buddy_count(), pjsua_get_buddy_count());
03664     printf("%s: ", title);
03665 
03666     fflush(stdout);
03667     if (fgets(buf, len, stdin) == NULL)
03668         return;
03669     len = strlen(buf);
03670 
03671     /* Left trim */
03672     while (pj_isspace(*buf)) {
03673         ++buf;
03674         --len;
03675     }
03676 
03677     /* Remove trailing newlines */
03678     while (len && (buf[len-1] == '\r' || buf[len-1] == '\n'))
03679         buf[--len] = '\0';
03680 
03681     if (len == 0 || buf[0]=='q')
03682         return;
03683 
03684     if (pj_isdigit(*buf) || *buf=='-') {
03685         
03686         int i;
03687         
03688         if (*buf=='-')
03689             i = 1;
03690         else
03691             i = 0;
03692 
03693         for (; i<len; ++i) {
03694             if (!pj_isdigit(buf[i])) {
03695                 puts("Invalid input");
03696                 return;
03697             }
03698         }
03699 
03700         result->nb_result = my_atoi(buf);
03701 
03702         if (result->nb_result >= 0 && 
03703             result->nb_result <= (int)pjsua_get_buddy_count()) 
03704         {
03705             return;
03706         }
03707         if (result->nb_result == -1)
03708             return;
03709 
03710         puts("Invalid input");
03711         result->nb_result = NO_NB;
03712         return;
03713 
03714     } else {
03715         pj_status_t status;
03716 
03717         if ((status=pjsua_verify_url(buf)) != PJ_SUCCESS) {
03718             pjsua_perror(THIS_FILE, "Invalid URL", status);
03719             return;
03720         }
03721 
03722         result->uri_result = buf;
03723     }
03724 }
03725 
03726 /*
03727  * List the ports in conference bridge
03728  */
03729 static void conf_list(void)
03730 {
03731     unsigned i, count;
03732     pjsua_conf_port_id id[PJSUA_MAX_CALLS];
03733 
03734     printf("Conference ports:\n");
03735 
03736     count = PJ_ARRAY_SIZE(id);
03737     pjsua_enum_conf_ports(id, &count);
03738 
03739     for (i=0; i<count; ++i) {
03740         char txlist[PJSUA_MAX_CALLS*4+10];
03741         unsigned j;
03742         pjsua_conf_port_info info;
03743 
03744         pjsua_conf_get_port_info(id[i], &info);
03745 
03746         txlist[0] = '\0';
03747         for (j=0; j<info.listener_cnt; ++j) {
03748             char s[10];
03749             pj_ansi_sprintf(s, "#%d ", info.listeners[j]);
03750             pj_ansi_strcat(txlist, s);
03751         }
03752         printf("Port #%02d[%2dKHz/%dms/%d] %20.*s  transmitting to: %s\n", 
03753                info.slot_id, 
03754                info.clock_rate/1000,
03755                info.samples_per_frame*1000/info.channel_count/info.clock_rate,
03756                info.channel_count,
03757                (int)info.name.slen, 
03758                info.name.ptr,
03759                txlist);
03760 
03761     }
03762     puts("");
03763 }
03764 
03765 
03766 /*
03767  * Send arbitrary request to remote host
03768  */
03769 static void send_request(char *cstr_method, const pj_str_t *dst_uri)
03770 {
03771     pj_str_t str_method;
03772     pjsip_method method;
03773     pjsip_tx_data *tdata;
03774     pjsip_endpoint *endpt;
03775     pj_status_t status;
03776 
03777     endpt = pjsua_get_pjsip_endpt();
03778 
03779     str_method = pj_str(cstr_method);
03780     pjsip_method_init_np(&method, &str_method);
03781 
03782     status = pjsua_acc_create_request(current_acc, &method, dst_uri, &tdata);
03783 
03784     status = pjsip_endpt_send_request(endpt, tdata, -1, NULL, NULL);
03785     if (status != PJ_SUCCESS) {
03786         pjsua_perror(THIS_FILE, "Unable to send request", status);
03787         return;
03788     }
03789 }
03790 
03791 
03792 /*
03793  * Change extended online status.
03794  */
03795 static void change_online_status(void)
03796 {
03797     char menuin[32];
03798     pj_bool_t online_status;
03799     pjrpid_element elem;
03800     int i, choice;
03801 
03802     enum {
03803         AVAILABLE, BUSY, OTP, IDLE, AWAY, BRB, OFFLINE, OPT_MAX
03804     };
03805 
03806     struct opt {
03807         int id;
03808         char *name;
03809     } opts[] = {
03810         { AVAILABLE, "Available" },
03811         { BUSY, "Busy"},
03812         { OTP, "On the phone"},
03813         { IDLE, "Idle"},
03814         { AWAY, "Away"},
03815         { BRB, "Be right back"},
03816         { OFFLINE, "Offline"}
03817     };
03818 
03819     printf("\n"
03820            "Choices:\n");
03821     for (i=0; i<PJ_ARRAY_SIZE(opts); ++i) {
03822         printf("  %d  %s\n", opts[i].id+1, opts[i].name);
03823     }
03824 
03825     if (!simple_input("Select status", menuin, sizeof(menuin)))
03826         return;
03827 
03828     choice = atoi(menuin) - 1;
03829     if (choice < 0 || choice >= OPT_MAX) {
03830         puts("Invalid selection");
03831         return;
03832     }
03833 
03834     pj_bzero(&elem, sizeof(elem));
03835     elem.type = PJRPID_ELEMENT_TYPE_PERSON;
03836 
03837     online_status = PJ_TRUE;
03838 
03839     switch (choice) {
03840     case AVAILABLE:
03841         break;
03842     case BUSY:
03843         elem.activity = PJRPID_ACTIVITY_BUSY;
03844         elem.note = pj_str("Busy");
03845         break;
03846     case OTP:
03847         elem.activity = PJRPID_ACTIVITY_BUSY;
03848         elem.note = pj_str("On the phone");
03849         break;
03850     case IDLE:
03851         elem.activity = PJRPID_ACTIVITY_UNKNOWN;
03852         elem.note = pj_str("Idle");
03853         break;
03854     case AWAY:
03855         elem.activity = PJRPID_ACTIVITY_AWAY;
03856         elem.note = pj_str("Away");
03857         break;
03858     case BRB:
03859         elem.activity = PJRPID_ACTIVITY_UNKNOWN;
03860         elem.note = pj_str("Be right back");
03861         break;
03862     case OFFLINE:
03863         online_status = PJ_FALSE;
03864         break;
03865     }
03866 
03867     pjsua_acc_set_online_status2(current_acc, online_status, &elem);
03868 }
03869 
03870 
03871 /*
03872  * Change codec priorities.
03873  */
03874 static void manage_codec_prio(void)
03875 {
03876     pjsua_codec_info c[32];
03877     unsigned i, count = PJ_ARRAY_SIZE(c);
03878     char input[32];
03879     char *codec, *prio;
03880     pj_str_t id;
03881     int new_prio;
03882     pj_status_t status;
03883 
03884     printf("List of audio codecs:\n");
03885     pjsua_enum_codecs(c, &count);
03886     for (i=0; i<count; ++i) {
03887         printf("  %d\t%.*s\n", c[i].priority, (int)c[i].codec_id.slen,
03888                                c[i].codec_id.ptr);
03889     }
03890 
03891 #if PJSUA_HAS_VIDEO
03892     puts("");
03893     printf("List of video codecs:\n");
03894     pjsua_vid_enum_codecs(c, &count);
03895     for (i=0; i<count; ++i) {
03896         printf("  %d\t%.*s%s%.*s\n", c[i].priority,
03897                                      (int)c[i].codec_id.slen,
03898                                      c[i].codec_id.ptr,
03899                                      c[i].desc.slen? " - ":"",
03900                                      (int)c[i].desc.slen,
03901                                      c[i].desc.ptr);
03902     }
03903 #endif
03904 
03905     puts("");
03906     puts("Enter codec id and its new priority (e.g. \"speex/16000 200\", ""\"H263 200\"),");
03907     puts("or empty to cancel.");
03908 
03909     printf("Codec name (\"*\" for all) and priority: ");
03910     if (fgets(input, sizeof(input), stdin) == NULL)
03911         return;
03912     if (input[0]=='\r' || input[0]=='\n') {
03913         puts("Done");
03914         return;
03915     }
03916 
03917     codec = strtok(input, " \t\r\n");
03918     prio = strtok(NULL, " \r\n");
03919 
03920     if (!codec || !prio) {
03921         puts("Invalid input");
03922         return;
03923     }
03924 
03925     new_prio = atoi(prio);
03926     if (new_prio < 0) 
03927         new_prio = 0;
03928     else if (new_prio > PJMEDIA_CODEC_PRIO_HIGHEST) 
03929         new_prio = PJMEDIA_CODEC_PRIO_HIGHEST;
03930 
03931     status = pjsua_codec_set_priority(pj_cstr(&id, codec), 
03932                                       (pj_uint8_t)new_prio);
03933 #if PJSUA_HAS_VIDEO
03934     if (status != PJ_SUCCESS) {
03935         status = pjsua_vid_codec_set_priority(pj_cstr(&id, codec), 
03936                                               (pj_uint8_t)new_prio);
03937     }
03938 #endif
03939     if (status != PJ_SUCCESS)
03940         pjsua_perror(THIS_FILE, "Error setting codec priority", status);
03941 }
03942 
03943 
03944 #if PJSUA_HAS_VIDEO
03945 static void vid_print_dev(int id, const pjmedia_vid_dev_info *vdi,
03946                           const char *title)
03947 {
03948     char capnames[120];
03949     char formats[120];
03950     const char *dirname;
03951     unsigned i;
03952 
03953     if (vdi->dir == PJMEDIA_DIR_CAPTURE_RENDER) {
03954         dirname = "capture, render";
03955     } else if (vdi->dir == PJMEDIA_DIR_CAPTURE) {
03956         dirname = "capture";
03957     } else {
03958         dirname = "render";
03959     }
03960 
03961 
03962     capnames[0] = '\0';
03963     for (i=0; i<sizeof(int)*8 && (1 << i) < PJMEDIA_VID_DEV_CAP_MAX; ++i) {
03964         if (vdi->caps & (1 << i)) {
03965             const char *capname = pjmedia_vid_dev_cap_name(1 << i, NULL);
03966             if (capname) {
03967                 if (*capnames)
03968                     strcat(capnames, ", ");
03969                 strncat(capnames, capname,
03970                         sizeof(capnames)-strlen(capnames)-1);
03971             }
03972         }
03973     }
03974 
03975     formats[0] = '\0';
03976     for (i=0; i<vdi->fmt_cnt; ++i) {
03977         const pjmedia_video_format_info *vfi =
03978                 pjmedia_get_video_format_info(NULL, vdi->fmt[i].id);
03979         if (vfi) {
03980             if (*formats)
03981                 strcat(formats, ", ");
03982             strncat(formats, vfi->name, sizeof(formats)-strlen(formats)-1);
03983         }
03984     }
03985 
03986     PJ_LOG(3,(THIS_FILE, "%3d %s [%s][%s] %s", id, vdi->name, vdi->driver,
03987               dirname, title));
03988     PJ_LOG(3,(THIS_FILE, "    Supported capabilities: %s", capnames));
03989     PJ_LOG(3,(THIS_FILE, "    Supported formats: %s", formats));
03990 }
03991 
03992 static void vid_list_devs(void)
03993 {
03994     unsigned i, count;
03995     pjmedia_vid_dev_info vdi;
03996     pj_status_t status;
03997 
03998     PJ_LOG(3,(THIS_FILE, "Video device list:"));
03999     count = pjsua_vid_dev_count();
04000     if (count == 0) {
04001         PJ_LOG(3,(THIS_FILE, " - no device detected -"));
04002         return;
04003     } else {
04004         PJ_LOG(3,(THIS_FILE, "%d device(s) detected:", count));
04005     }
04006 
04007     status = pjsua_vid_dev_get_info(PJMEDIA_VID_DEFAULT_RENDER_DEV, &vdi);
04008     if (status == PJ_SUCCESS)
04009         vid_print_dev(PJMEDIA_VID_DEFAULT_RENDER_DEV, &vdi,
04010                       "(default renderer device)");
04011 
04012     status = pjsua_vid_dev_get_info(PJMEDIA_VID_DEFAULT_CAPTURE_DEV, &vdi);
04013     if (status == PJ_SUCCESS)
04014         vid_print_dev(PJMEDIA_VID_DEFAULT_CAPTURE_DEV, &vdi,
04015                       "(default capture device)");
04016 
04017     for (i=0; i<count; ++i) {
04018         status = pjsua_vid_dev_get_info(i, &vdi);
04019         if (status == PJ_SUCCESS)
04020             vid_print_dev(i, &vdi, "");
04021     }
04022 }
04023 
04024 static void app_config_init_video(pjsua_acc_config *acc_cfg)
04025 {
04026     acc_cfg->vid_in_auto_show = app_config.vid.in_auto_show;
04027     acc_cfg->vid_out_auto_transmit = app_config.vid.out_auto_transmit;
04028     /* Note that normally GUI application will prefer a borderless
04029      * window.
04030      */
04031     acc_cfg->vid_wnd_flags = PJMEDIA_VID_DEV_WND_BORDER |
04032                              PJMEDIA_VID_DEV_WND_RESIZABLE;
04033     acc_cfg->vid_cap_dev = app_config.vid.vcapture_dev;
04034     acc_cfg->vid_rend_dev = app_config.vid.vrender_dev;
04035 
04036     if (app_config.avi_auto_play &&
04037         app_config.avi_def_idx != PJSUA_INVALID_ID &&
04038         app_config.avi[app_config.avi_def_idx].dev_id != PJMEDIA_VID_INVALID_DEV)
04039     {
04040         acc_cfg->vid_cap_dev = app_config.avi[app_config.avi_def_idx].dev_id;
04041     }
04042 }
04043 
04044 static void app_config_show_video(int acc_id, const pjsua_acc_config *acc_cfg)
04045 {
04046     PJ_LOG(3,(THIS_FILE,
04047               "Account %d:\n"
04048               "  RX auto show:     %d\n"
04049               "  TX auto transmit: %d\n"
04050               "  Capture dev:      %d\n"
04051               "  Render dev:       %d",
04052               acc_id,
04053               acc_cfg->vid_in_auto_show,
04054               acc_cfg->vid_out_auto_transmit,
04055               acc_cfg->vid_cap_dev,
04056               acc_cfg->vid_rend_dev));
04057 }
04058 
04059 static void vid_handle_menu(char *menuin)
04060 {
04061     char *argv[8];
04062     int argc = 0;
04063 
04064     /* Tokenize */
04065     argv[argc] = strtok(menuin, " \t\r\n");
04066     while (argv[argc] && *argv[argc]) {
04067         argc++;
04068         argv[argc] = strtok(NULL, " \t\r\n");
04069     }
04070 
04071     if (argc == 1 || strcmp(argv[1], "help")==0) {
04072         vid_show_help();
04073     } else if (argc == 2 && (strcmp(argv[1], "enable")==0 ||
04074                              strcmp(argv[1], "disable")==0))
04075     {
04076         pj_bool_t enabled = (strcmp(argv[1], "enable")==0);
04077         app_config.vid.vid_cnt = (enabled ? 1 : 0);
04078         PJ_LOG(3,(THIS_FILE, "Video will be %s in next offer/answer",
04079                   (enabled?"enabled":"disabled")));
04080     } else if (strcmp(argv[1], "acc")==0) {
04081         pjsua_acc_config acc_cfg;
04082         pj_bool_t changed = PJ_FALSE;
04083 
04084         pjsua_acc_get_config(current_acc, &acc_cfg);
04085 
04086         if (argc == 3 && strcmp(argv[2], "show")==0) {
04087             app_config_show_video(current_acc, &acc_cfg);
04088         } else if (argc == 4 && strcmp(argv[2], "autorx")==0) {
04089             int on = (strcmp(argv[3], "on")==0);
04090             acc_cfg.vid_in_auto_show = on;
04091             changed = PJ_TRUE;
04092         } else if (argc == 4 && strcmp(argv[2], "autotx")==0) {
04093             int on = (strcmp(argv[3], "on")==0);
04094             acc_cfg.vid_out_auto_transmit = on;
04095             changed = PJ_TRUE;
04096         } else if (argc == 4 && strcmp(argv[2], "cap")==0) {
04097             int dev = atoi(argv[3]);
04098             acc_cfg.vid_cap_dev = dev;
04099             changed = PJ_TRUE;
04100         } else if (argc == 4 && strcmp(argv[2], "rend")==0) {
04101             int dev = atoi(argv[3]);
04102             acc_cfg.vid_rend_dev = dev;
04103             changed = PJ_TRUE;
04104         } else {
04105             goto on_error;
04106         }
04107 
04108         if (changed) {
04109             pj_status_t status = pjsua_acc_modify(current_acc, &acc_cfg);
04110             if (status != PJ_SUCCESS)
04111                 PJ_PERROR(1,(THIS_FILE, status, "Error modifying account %d",
04112                              current_acc));
04113         }
04114 
04115     } else if (strcmp(argv[1], "call")==0) {
04116         pjsua_call_vid_strm_op_param param;
04117         pj_status_t status = PJ_SUCCESS;
04118 
04119         pjsua_call_vid_strm_op_param_default(&param);
04120 
04121         if (argc == 5 && strcmp(argv[2], "rx")==0) {
04122             pjsua_stream_info si;
04123             pj_bool_t on = (strcmp(argv[3], "on") == 0);
04124 
04125             param.med_idx = atoi(argv[4]);
04126             if (pjsua_call_get_stream_info(current_call, param.med_idx, &si) ||
04127                 si.type != PJMEDIA_TYPE_VIDEO)
04128             {
04129                 PJ_PERROR(1,(THIS_FILE, PJ_EINVAL, "Invalid stream"));
04130                 return;
04131             }
04132 
04133             if (on) param.dir = (si.info.vid.dir | PJMEDIA_DIR_DECODING);
04134             else param.dir = (si.info.vid.dir & PJMEDIA_DIR_ENCODING);
04135 
04136             status = pjsua_call_set_vid_strm(current_call,
04137                                              PJSUA_CALL_VID_STRM_CHANGE_DIR,
04138                                              &param);
04139         }
04140         else if (argc == 5 && strcmp(argv[2], "tx")==0) {
04141             pj_bool_t on = (strcmp(argv[3], "on") == 0);
04142             pjsua_call_vid_strm_op op = on? PJSUA_CALL_VID_STRM_START_TRANSMIT :
04143                                             PJSUA_CALL_VID_STRM_STOP_TRANSMIT;
04144 
04145             param.med_idx = atoi(argv[4]);
04146 
04147             status = pjsua_call_set_vid_strm(current_call, op, &param);
04148         }
04149         else if (argc == 3 && strcmp(argv[2], "add")==0) {
04150             status = pjsua_call_set_vid_strm(current_call,
04151                                              PJSUA_CALL_VID_STRM_ADD, NULL);
04152         }
04153         else if (argc >= 3 && 
04154                  (strcmp(argv[2], "disable")==0 || strcmp(argv[2], "enable")==0))
04155         {
04156             pj_bool_t enable = (strcmp(argv[2], "enable") == 0);
04157             pjsua_call_vid_strm_op op = enable? PJSUA_CALL_VID_STRM_CHANGE_DIR :
04158                                                 PJSUA_CALL_VID_STRM_REMOVE;
04159 
04160             param.med_idx = argc >= 4? atoi(argv[3]) : -1;
04161             param.dir = PJMEDIA_DIR_ENCODING_DECODING;
04162             status = pjsua_call_set_vid_strm(current_call, op, &param);
04163         }
04164         else if (argc >= 3 && strcmp(argv[2], "cap")==0) {
04165             param.med_idx = argc >= 4? atoi(argv[3]) : -1;
04166             param.cap_dev = argc >= 5? atoi(argv[4]) : PJMEDIA_VID_DEFAULT_CAPTURE_DEV;
04167             status = pjsua_call_set_vid_strm(current_call,
04168                                              PJSUA_CALL_VID_STRM_CHANGE_CAP_DEV,
04169                                              &param);
04170         } else
04171             goto on_error;
04172 
04173         if (status != PJ_SUCCESS) {
04174             PJ_PERROR(1,(THIS_FILE, status, "Error modifying video stream"));
04175         }
04176 
04177     } else if (argc >= 3 && strcmp(argv[1], "dev")==0) {
04178         if (strcmp(argv[2], "list")==0) {
04179             vid_list_devs();
04180         } else if (strcmp(argv[2], "refresh")==0) {
04181             pjmedia_vid_dev_refresh();
04182         } else if (strcmp(argv[2], "prev")==0) {
04183             if (argc != 5) {
04184                 goto on_error;
04185             } else {
04186                 pj_bool_t on = (strcmp(argv[3], "on") == 0);
04187                 int dev_id = atoi(argv[4]);
04188                 if (on) {
04189                     pjsua_vid_preview_param param;
04190 
04191                     pjsua_vid_preview_param_default(&param);
04192                     param.wnd_flags = PJMEDIA_VID_DEV_WND_BORDER |
04193                                       PJMEDIA_VID_DEV_WND_RESIZABLE;
04194                     pjsua_vid_preview_start(dev_id, &param);
04195                     arrange_window(pjsua_vid_preview_get_win(dev_id));
04196                 } else {
04197                     pjsua_vid_win_id wid;
04198                     wid = pjsua_vid_preview_get_win(dev_id);
04199                     if (wid != PJSUA_INVALID_ID) {
04200                         /* Preview window hiding once it is stopped is
04201                          * responsibility of app */
04202                         pjsua_vid_win_set_show(wid, PJ_FALSE);
04203                         pjsua_vid_preview_stop(dev_id);
04204                     }
04205                 }
04206             }
04207         } else
04208             goto on_error;
04209     } else if (strcmp(argv[1], "win")==0) {
04210         pj_status_t status = PJ_SUCCESS;
04211 
04212         if (argc==3 && strcmp(argv[2], "list")==0) {
04213             pjsua_vid_win_id wids[PJSUA_MAX_VID_WINS];
04214             unsigned i, cnt = PJ_ARRAY_SIZE(wids);
04215 
04216             pjsua_vid_enum_wins(wids, &cnt);
04217 
04218             PJ_LOG(3,(THIS_FILE, "Found %d video windows:", cnt));
04219             PJ_LOG(3,(THIS_FILE, "WID show    pos       size"));
04220             PJ_LOG(3,(THIS_FILE, "------------------------------"));
04221             for (i = 0; i < cnt; ++i) {
04222                 pjsua_vid_win_info wi;
04223                 pjsua_vid_win_get_info(wids[i], &wi);
04224                 PJ_LOG(3,(THIS_FILE, "%3d   %c  (%d,%d)  %dx%d",
04225                           wids[i], (wi.show?'Y':'N'), wi.pos.x, wi.pos.y,
04226                           wi.size.w, wi.size.h));
04227             }
04228         } else if (argc==4 && (strcmp(argv[2], "show")==0 ||
04229                                strcmp(argv[2], "hide")==0))
04230         {
04231             pj_bool_t show = (strcmp(argv[2], "show")==0);
04232             pjsua_vid_win_id wid = atoi(argv[3]);
04233             status = pjsua_vid_win_set_show(wid, show);
04234         } else if (argc==6 && strcmp(argv[2], "move")==0) {
04235             pjsua_vid_win_id wid = atoi(argv[3]);
04236             pjmedia_coord pos;
04237 
04238             pos.x = atoi(argv[4]);
04239             pos.y = atoi(argv[5]);
04240             status = pjsua_vid_win_set_pos(wid, &pos);
04241         } else if (argc==6 && strcmp(argv[2], "resize")==0) {
04242             pjsua_vid_win_id wid = atoi(argv[3]);
04243             pjmedia_rect_size size;
04244 
04245             size.w = atoi(argv[4]);
04246             size.h = atoi(argv[5]);
04247             status = pjsua_vid_win_set_size(wid, &size);
04248         } else if (argc==3 && strcmp(argv[2], "arrange")==0) {
04249             arrange_window(PJSUA_INVALID_ID);
04250         } else
04251             goto on_error;
04252 
04253         if (status != PJ_SUCCESS) {
04254             PJ_PERROR(1,(THIS_FILE, status, "Window operation error"));
04255         }
04256 
04257     } else if (strcmp(argv[1], "codec")==0) {
04258         pjsua_codec_info ci[PJMEDIA_CODEC_MGR_MAX_CODECS];
04259         unsigned count = PJ_ARRAY_SIZE(ci);
04260         pj_status_t status;
04261 
04262         if (argc==3 && strcmp(argv[2], "list")==0) {
04263             status = pjsua_vid_enum_codecs(ci, &count);
04264             if (status != PJ_SUCCESS) {
04265                 PJ_PERROR(1,(THIS_FILE, status, "Error enumerating codecs"));
04266             } else {
04267                 unsigned i;
04268                 PJ_LOG(3,(THIS_FILE, "Found %d video codecs:", count));
04269                 PJ_LOG(3,(THIS_FILE, "codec id      prio  fps    bw(kbps)   size"));
04270                 PJ_LOG(3,(THIS_FILE, "------------------------------------------"));
04271                 for (i=0; i<count; ++i) {
04272                     pjmedia_vid_codec_param cp;
04273                     pjmedia_video_format_detail *vfd;
04274 
04275                     status = pjsua_vid_codec_get_param(&ci[i].codec_id, &cp);
04276                     if (status != PJ_SUCCESS)
04277                         continue;
04278 
04279                     vfd = pjmedia_format_get_video_format_detail(&cp.enc_fmt,
04280                                                                  PJ_TRUE);
04281                     PJ_LOG(3,(THIS_FILE, "%.*s%.*s %3d %7.2f  %4d/%4d  %dx%d", 
04282                               (int)ci[i].codec_id.slen, ci[i].codec_id.ptr,
04283                               13-(int)ci[i].codec_id.slen, "                ",
04284                               ci[i].priority,
04285                               (vfd->fps.num*1.0/vfd->fps.denum),
04286                               vfd->avg_bps/1000, vfd->max_bps/1000,
04287                               vfd->size.w, vfd->size.h));
04288                 }
04289             }
04290         } else if (argc==5 && strcmp(argv[2], "prio")==0) {
04291             pj_str_t cid;
04292             int prio;
04293             cid = pj_str(argv[3]);
04294             prio = atoi(argv[4]);
04295             status = pjsua_vid_codec_set_priority(&cid, (pj_uint8_t)prio);
04296             if (status != PJ_SUCCESS)
04297                 PJ_PERROR(1,(THIS_FILE, status, "Set codec priority error"));
04298         } else if (argc==6 && strcmp(argv[2], "fps")==0) {
04299             pjmedia_vid_codec_param cp;
04300             pj_str_t cid;
04301             int M, N;
04302             cid = pj_str(argv[3]);
04303             M = atoi(argv[4]);
04304             N = atoi(argv[5]);
04305             status = pjsua_vid_codec_get_param(&cid, &cp);
04306             if (status == PJ_SUCCESS) {
04307                 cp.enc_fmt.det.vid.fps.num = M;
04308                 cp.enc_fmt.det.vid.fps.denum = N;
04309                 status = pjsua_vid_codec_set_param(&cid, &cp);
04310             }
04311             if (status != PJ_SUCCESS)
04312                 PJ_PERROR(1,(THIS_FILE, status, "Set codec framerate error"));
04313         } else if (argc==6 && strcmp(argv[2], "bw")==0) {
04314             pjmedia_vid_codec_param cp;
04315             pj_str_t cid;
04316             int M, N;
04317             cid = pj_str(argv[3]);
04318             M = atoi(argv[4]);
04319             N = atoi(argv[5]);
04320             status = pjsua_vid_codec_get_param(&cid, &cp);
04321             if (status == PJ_SUCCESS) {
04322                 cp.enc_fmt.det.vid.avg_bps = M * 1000;
04323                 cp.enc_fmt.det.vid.max_bps = N * 1000;
04324                 status = pjsua_vid_codec_set_param(&cid, &cp);
04325             }
04326             if (status != PJ_SUCCESS)
04327                 PJ_PERROR(1,(THIS_FILE, status, "Set codec bitrate error"));
04328         } else if (argc==6 && strcmp(argv[2], "size")==0) {
04329             pjmedia_vid_codec_param cp;
04330             pj_str_t cid;
04331             int M, N;
04332             cid = pj_str(argv[3]);
04333             M = atoi(argv[4]);
04334             N = atoi(argv[5]);
04335             status = pjsua_vid_codec_get_param(&cid, &cp);
04336             if (status == PJ_SUCCESS) {
04337                 cp.enc_fmt.det.vid.size.w = M;
04338                 cp.enc_fmt.det.vid.size.h = N;
04339                 status = pjsua_vid_codec_set_param(&cid, &cp);
04340             }
04341             if (status != PJ_SUCCESS)
04342                 PJ_PERROR(1,(THIS_FILE, status, "Set codec bitrate error"));
04343         } else
04344             goto on_error;
04345     } else
04346         goto on_error;
04347 
04348     return;
04349 
04350 on_error:
04351     PJ_LOG(1,(THIS_FILE, "Invalid command, use 'vid help'"));
04352 }
04353 
04354 #else
04355 
04356 static void app_config_init_video(pjsua_acc_config *acc_cfg)
04357 {
04358     PJ_UNUSED_ARG(acc_cfg);
04359 }
04360 
04361 #endif /* PJSUA_HAS_VIDEO */
04362 
04363 
04364 /*
04365  * Main "user interface" loop.
04366  */
04367 void console_app_main(const pj_str_t *uri_to_call)
04368 {
04369     char menuin[32];
04370     char buf[128];
04371     char text[128];
04372     int i, count;
04373     char *uri;
04374     pj_str_t tmp;
04375     struct input_result result;
04376     pjsua_msg_data msg_data;
04377     pjsua_call_info call_info;
04378     pjsua_acc_info acc_info;
04379     pjsua_call_setting call_opt;
04380 
04381     pjsua_call_setting_default(&call_opt);
04382     call_opt.aud_cnt = app_config.aud_cnt;
04383     call_opt.vid_cnt = app_config.vid.vid_cnt;
04384 
04385     /* If user specifies URI to call, then call the URI */
04386     if (uri_to_call->slen) {
04387         pjsua_call_make_call( current_acc, uri_to_call, &call_opt, NULL, NULL, NULL);
04388     }
04389 
04390     keystroke_help();
04391 
04392     for (;;) {
04393 
04394         printf(">>> ");
04395         fflush(stdout);
04396 
04397         if (fgets(menuin, sizeof(menuin), stdin) == NULL) {
04398             /* 
04399              * Be friendly to users who redirect commands into
04400              * program, when file ends, resume with kbd.
04401              * If exit is desired end script with q for quit
04402              */
04403             /* Reopen stdin/stdout/stderr to /dev/console */
04404 #if defined(PJ_WIN32) && PJ_WIN32!=0
04405             if (freopen ("CONIN$", "r", stdin) == NULL) {
04406 #else
04407             if (1) {
04408 #endif
04409                 puts("Cannot switch back to console from file redirection");
04410                 menuin[0] = 'q';
04411                 menuin[1] = '\0';
04412             } else {
04413                 puts("Switched back to console from file redirection");
04414                 continue;
04415             }
04416         }
04417 
04418         if (cmd_echo) {
04419             printf("%s", menuin);
04420         }
04421 
04422         /* Update call setting */
04423         pjsua_call_setting_default(&call_opt);
04424         call_opt.aud_cnt = app_config.aud_cnt;
04425         call_opt.vid_cnt = app_config.vid.vid_cnt;
04426 
04427         switch (menuin[0]) {
04428 
04429         case 'm':
04430             /* Make call! : */
04431             printf("(You currently have %d calls)\n", 
04432                      pjsua_call_get_count());
04433             
04434             uri = NULL;
04435             ui_input_url("Make call", buf, sizeof(buf), &result);
04436             if (result.nb_result != NO_NB) {
04437 
04438                 if (result.nb_result == -1 || result.nb_result == 0) {
04439                     puts("You can't do that with make call!");
04440                     continue;
04441                 } else {
04442                     pjsua_buddy_info binfo;
04443                     pjsua_buddy_get_info(result.nb_result-1, &binfo);
04444                     tmp.ptr = buf;
04445                     pj_strncpy(&tmp, &binfo.uri, sizeof(buf));
04446                 }
04447 
04448             } else if (result.uri_result) {
04449                 tmp = pj_str(result.uri_result);
04450             } else {
04451                 tmp.slen = 0;
04452             }
04453             
04454             pjsua_msg_data_init(&msg_data);
04455             TEST_MULTIPART(&msg_data);
04456             pjsua_call_make_call( current_acc, &tmp, &call_opt, NULL, &msg_data, NULL);
04457             break;
04458 
04459         case 'M':
04460             /* Make multiple calls! : */
04461             printf("(You currently have %d calls)\n", 
04462                    pjsua_call_get_count());
04463             
04464             if (!simple_input("Number of calls", menuin, sizeof(menuin)))
04465                 continue;
04466 
04467             count = my_atoi(menuin);
04468             if (count < 1)
04469                 continue;
04470 
04471             ui_input_url("Make call", buf, sizeof(buf), &result);
04472             if (result.nb_result != NO_NB) {
04473                 pjsua_buddy_info binfo;
04474                 if (result.nb_result == -1 || result.nb_result == 0) {
04475                     puts("You can't do that with make call!");
04476                     continue;
04477                 }
04478                 pjsua_buddy_get_info(result.nb_result-1, &binfo);
04479                 tmp.ptr = buf;
04480                 pj_strncpy(&tmp, &binfo.uri, sizeof(buf));
04481             } else {
04482                 tmp = pj_str(result.uri_result);
04483             }
04484 
04485             for (i=0; i<my_atoi(menuin); ++i) {
04486                 pj_status_t status;
04487             
04488                 status = pjsua_call_make_call(current_acc, &tmp, &call_opt, NULL,
04489                                               NULL, NULL);
04490                 if (status != PJ_SUCCESS)
04491                     break;
04492             }
04493             break;
04494 
04495         case 'n':
04496             i = pjsua_detect_nat_type();
04497             if (i != PJ_SUCCESS)
04498                 pjsua_perror(THIS_FILE, "Error", i);
04499             break;
04500 
04501         case 'i':
04502             /* Send instant messaeg */
04503 
04504             /* i is for call index to send message, if any */
04505             i = -1;
04506     
04507             /* Make compiler happy. */
04508             uri = NULL;
04509 
04510             /* Input destination. */
04511             ui_input_url("Send IM to", buf, sizeof(buf), &result);
04512             if (result.nb_result != NO_NB) {
04513 
04514                 if (result.nb_result == -1) {
04515                     puts("You can't send broadcast IM like that!");
04516                     continue;
04517 
04518                 } else if (result.nb_result == 0) {
04519     
04520                     i = current_call;
04521 
04522                 } else {
04523                     pjsua_buddy_info binfo;
04524                     pjsua_buddy_get_info(result.nb_result-1, &binfo);
04525                     tmp.ptr = buf;
04526                     pj_strncpy_with_null(&tmp, &binfo.uri, sizeof(buf));
04527                     uri = buf;
04528                 }
04529 
04530             } else if (result.uri_result) {
04531                 uri = result.uri_result;
04532             }
04533             
04534 
04535             /* Send typing indication. */
04536             if (i != -1)
04537                 pjsua_call_send_typing_ind(i, PJ_TRUE, NULL);
04538             else {
04539                 pj_str_t tmp_uri = pj_str(uri);
04540                 pjsua_im_typing(current_acc, &tmp_uri, PJ_TRUE, NULL);
04541             }
04542 
04543             /* Input the IM . */
04544             if (!simple_input("Message", text, sizeof(text))) {
04545                 /*
04546                  * Cancelled.
04547                  * Send typing notification too, saying we're not typing.
04548                  */
04549                 if (i != -1)
04550                     pjsua_call_send_typing_ind(i, PJ_FALSE, NULL);
04551                 else {
04552                     pj_str_t tmp_uri = pj_str(uri);
04553                     pjsua_im_typing(current_acc, &tmp_uri, PJ_FALSE, NULL);
04554                 }
04555                 continue;
04556             }
04557 
04558             tmp = pj_str(text);
04559 
04560             /* Send the IM */
04561             if (i != -1)
04562                 pjsua_call_send_im(i, NULL, &tmp, NULL, NULL);
04563             else {
04564                 pj_str_t tmp_uri = pj_str(uri);
04565                 pjsua_im_send(current_acc, &tmp_uri, NULL, &tmp, NULL, NULL);
04566             }
04567 
04568             break;
04569 
04570         case 'a':
04571 
04572             if (current_call != -1) {
04573                 pjsua_call_get_info(current_call, &call_info);
04574             } else {
04575                 /* Make compiler happy */
04576                 call_info.role = PJSIP_ROLE_UAC;
04577                 call_info.state = PJSIP_INV_STATE_DISCONNECTED;
04578             }
04579 
04580             if (current_call == -1 || 
04581                 call_info.role != PJSIP_ROLE_UAS ||
04582                 call_info.state >= PJSIP_INV_STATE_CONNECTING)
04583             {
04584                 puts("No pending incoming call");
04585                 fflush(stdout);
04586                 continue;
04587 
04588             } else {
04589                 int st_code;
04590                 char contact[120];
04591                 pj_str_t hname = { "Contact", 7 };
04592                 pj_str_t hvalue;
04593                 pjsip_generic_string_hdr hcontact;
04594 
04595                 if (!simple_input("Answer with code (100-699)", buf, sizeof(buf)))
04596                     continue;
04597                 
04598                 st_code = my_atoi(buf);
04599                 if (st_code < 100)
04600                     continue;
04601 
04602                 pjsua_msg_data_init(&msg_data);
04603 
04604                 if (st_code/100 == 3) {
04605                     if (!simple_input("Enter URL to be put in Contact", 
04606                                       contact, sizeof(contact)))
04607                         continue;
04608                     hvalue = pj_str(contact);
04609                     pjsip_generic_string_hdr_init2(&hcontact, &hname, &hvalue);
04610 
04611                     pj_list_push_back(&msg_data.hdr_list, &hcontact);
04612                 }
04613 
04614                 /*
04615                  * Must check again!
04616                  * Call may have been disconnected while we're waiting for 
04617                  * keyboard input.
04618                  */
04619                 if (current_call == -1) {
04620                     puts("Call has been disconnected");
04621                     fflush(stdout);
04622                     continue;
04623                 }
04624 
04625                 pjsua_call_answer2(current_call, &call_opt, st_code, NULL, &msg_data);
04626             }
04627 
04628             break;
04629 
04630 
04631         case 'h':
04632 
04633             if (current_call == -1) {
04634                 puts("No current call");
04635                 fflush(stdout);
04636                 continue;
04637 
04638             } else if (menuin[1] == 'a') {
04639                 
04640                 /* Hangup all calls */
04641                 pjsua_call_hangup_all();
04642 
04643             } else {
04644 
04645                 /* Hangup current calls */
04646                 pjsua_call_hangup(current_call, 0, NULL, NULL);
04647             }
04648             break;
04649 
04650         case ']':
04651         case '[':
04652             /*
04653              * Cycle next/prev dialog.
04654              */
04655             if (menuin[0] == ']') {
04656                 find_next_call();
04657 
04658             } else {
04659                 find_prev_call();
04660             }
04661 
04662             if (current_call != -1) {
04663                 
04664                 pjsua_call_get_info(current_call, &call_info);
04665                 PJ_LOG(3,(THIS_FILE,"Current dialog: %.*s", 
04666                           (int)call_info.remote_info.slen, 
04667                           call_info.remote_info.ptr));
04668 
04669             } else {
04670                 PJ_LOG(3,(THIS_FILE,"No current dialog"));
04671             }
04672             break;
04673 
04674 
04675         case '>':
04676         case '<':
04677             if (!simple_input("Enter account ID to select", buf, sizeof(buf)))
04678                 break;
04679 
04680             i = my_atoi(buf);
04681             if (pjsua_acc_is_valid(i)) {
04682                 pjsua_acc_set_default(i);
04683                 PJ_LOG(3,(THIS_FILE, "Current account changed to %d", i));
04684             } else {
04685                 PJ_LOG(3,(THIS_FILE, "Invalid account id %d", i));
04686             }
04687             break;
04688 
04689 
04690         case '+':
04691             if (menuin[1] == 'b') {
04692             
04693                 pjsua_buddy_config buddy_cfg;
04694                 pjsua_buddy_id buddy_id;
04695                 pj_status_t status;
04696 
04697                 if (!simple_input("Enter buddy's URI:", buf, sizeof(buf)))
04698                     break;
04699 
04700                 if (pjsua_verify_url(buf) != PJ_SUCCESS) {
04701                     printf("Invalid URI '%s'\n", buf);
04702                     break;
04703                 }
04704 
04705                 pj_bzero(&buddy_cfg, sizeof(pjsua_buddy_config));
04706 
04707                 buddy_cfg.uri = pj_str(buf);
04708                 buddy_cfg.subscribe = PJ_TRUE;
04709 
04710                 status = pjsua_buddy_add(&buddy_cfg, &buddy_id);
04711                 if (status == PJ_SUCCESS) {
04712                     printf("New buddy '%s' added at index %d\n",
04713                            buf, buddy_id+1);
04714                 }
04715 
04716             } else if (menuin[1] == 'a') {
04717 
04718                 char id[80], registrar[80], realm[80], uname[80], passwd[30];
04719                 pjsua_acc_config acc_cfg;
04720                 pj_status_t status;
04721 
04722                 if (!simple_input("Your SIP URL:", id, sizeof(id)))
04723                     break;
04724                 if (!simple_input("URL of the registrar:", registrar, sizeof(registrar)))
04725                     break;
04726                 if (!simple_input("Auth Realm:", realm, sizeof(realm)))
04727                     break;
04728                 if (!simple_input("Auth Username:", uname, sizeof(uname)))
04729                     break;
04730                 if (!simple_input("Auth Password:", passwd, sizeof(passwd)))
04731                     break;
04732 
04733                 pjsua_acc_config_default(&acc_cfg);
04734                 acc_cfg.id = pj_str(id);
04735                 acc_cfg.reg_uri = pj_str(registrar);
04736                 acc_cfg.cred_count = 1;
04737                 acc_cfg.cred_info[0].scheme = pj_str("Digest");
04738                 acc_cfg.cred_info[0].realm = pj_str(realm);
04739                 acc_cfg.cred_info[0].username = pj_str(uname);
04740                 acc_cfg.cred_info[0].data_type = 0;
04741                 acc_cfg.cred_info[0].data = pj_str(passwd);
04742 
04743                 acc_cfg.rtp_cfg = app_config.rtp_cfg;
04744                 app_config_init_video(&acc_cfg);
04745 
04746                 status = pjsua_acc_add(&acc_cfg, PJ_TRUE, NULL);
04747                 if (status != PJ_SUCCESS) {
04748                     pjsua_perror(THIS_FILE, "Error adding new account", status);
04749                 }
04750 
04751             } else {
04752                 printf("Invalid input %s\n", menuin);
04753             }
04754             break;
04755 
04756         case '-':
04757             if (menuin[1] == 'b') {
04758                 if (!simple_input("Enter buddy ID to delete",buf,sizeof(buf)))
04759                     break;
04760 
04761                 i = my_atoi(buf) - 1;
04762 
04763                 if (!pjsua_buddy_is_valid(i)) {
04764                     printf("Invalid buddy id %d\n", i);
04765                 } else {
04766                     pjsua_buddy_del(i);
04767                     printf("Buddy %d deleted\n", i);
04768                 }
04769 
04770             } else if (menuin[1] == 'a') {
04771 
04772                 if (!simple_input("Enter account ID to delete",buf,sizeof(buf)))
04773                     break;
04774 
04775                 i = my_atoi(buf);
04776 
04777                 if (!pjsua_acc_is_valid(i)) {
04778                     printf("Invalid account id %d\n", i);
04779                 } else {
04780                     pjsua_acc_del(i);
04781                     printf("Account %d deleted\n", i);
04782                 }
04783 
04784             } else {
04785                 printf("Invalid input %s\n", menuin);
04786             }
04787             break;
04788 
04789         case 'H':
04790             /*
04791              * Hold call.
04792              */
04793             if (current_call != -1) {
04794                 
04795                 pjsua_call_set_hold(current_call, NULL);
04796 
04797             } else {
04798                 PJ_LOG(3,(THIS_FILE, "No current call"));
04799             }
04800             break;
04801 
04802         case 'v':
04803 #if PJSUA_HAS_VIDEO
04804             if (menuin[1]=='i' && menuin[2]=='d' && menuin[3]==' ') {
04805 
04806                 vid_handle_menu(menuin);
04807 
04808             } else
04809 #endif
04810             if (current_call != -1) {
04811                 /*
04812                  * re-INVITE
04813                  */
04814                 call_opt.flag |= PJSUA_CALL_UNHOLD;
04815                 pjsua_call_reinvite2(current_call, &call_opt, NULL);
04816 
04817             } else {
04818                 PJ_LOG(3,(THIS_FILE, "No current call"));
04819             }
04820             break;
04821 
04822         case 'U':
04823             /*
04824              * Send UPDATE
04825              */
04826             if (current_call != -1) {
04827                 
04828                 pjsua_call_update2(current_call, &call_opt, NULL);
04829 
04830             } else {
04831                 PJ_LOG(3,(THIS_FILE, "No current call"));
04832             }
04833             break;
04834 
04835         case 'C':
04836             if (menuin[1] == 'p') {
04837                 manage_codec_prio();
04838             }
04839             break;
04840 
04841         case 'x':
04842             /*
04843              * Transfer call.
04844              */
04845             if (current_call == -1) {
04846                 
04847                 PJ_LOG(3,(THIS_FILE, "No current call"));
04848 
04849             } else {
04850                 int call = current_call;
04851                 pjsip_generic_string_hdr refer_sub;
04852                 pj_str_t STR_REFER_SUB = { "Refer-Sub", 9 };
04853                 pj_str_t STR_FALSE = { "false", 5 };
04854                 pjsua_call_info ci;
04855 
04856                 pjsua_call_get_info(current_call, &ci);
04857                 printf("Transfering current call [%d] %.*s\n",
04858                        current_call,
04859                        (int)ci.remote_info.slen, ci.remote_info.ptr);
04860 
04861                 ui_input_url("Transfer to URL", buf, sizeof(buf), &result);
04862 
04863                 /* Check if call is still there. */
04864 
04865                 if (call != current_call) {
04866                     puts("Call has been disconnected");
04867                     continue;
04868                 }
04869 
04870                 pjsua_msg_data_init(&msg_data);
04871                 if (app_config.no_refersub) {
04872                     /* Add Refer-Sub: false in outgoing REFER request */
04873                     pjsip_generic_string_hdr_init2(&refer_sub, &STR_REFER_SUB,
04874                                                    &STR_FALSE);
04875                     pj_list_push_back(&msg_data.hdr_list, &refer_sub);
04876                 }
04877                 if (result.nb_result != NO_NB) {
04878                     if (result.nb_result == -1 || result.nb_result == 0)
04879                         puts("You can't do that with transfer call!");
04880                     else {
04881                         pjsua_buddy_info binfo;
04882                         pjsua_buddy_get_info(result.nb_result-1, &binfo);
04883                         pjsua_call_xfer( current_call, &binfo.uri, &msg_data);
04884                     }
04885 
04886                 } else if (result.uri_result) {
04887                     pj_str_t tmp;
04888                     tmp = pj_str(result.uri_result);
04889                     pjsua_call_xfer( current_call, &tmp, &msg_data);
04890                 }
04891             }
04892             break;
04893 
04894         case 'X':
04895             /*
04896              * Transfer call with replaces.
04897              */
04898             if (current_call == -1) {
04899                 
04900                 PJ_LOG(3,(THIS_FILE, "No current call"));
04901 
04902             } else {
04903                 int call = current_call;
04904                 int dst_call;
04905                 pjsip_generic_string_hdr refer_sub;
04906                 pj_str_t STR_REFER_SUB = { "Refer-Sub", 9 };
04907                 pj_str_t STR_FALSE = { "false", 5 };
04908                 pjsua_call_id ids[PJSUA_MAX_CALLS];
04909                 pjsua_call_info ci;
04910                 unsigned i, count;
04911 
04912                 count = PJ_ARRAY_SIZE(ids);
04913                 pjsua_enum_calls(ids, &count);
04914 
04915                 if (count <= 1) {
04916                     puts("There are no other calls");
04917                     continue;
04918                 }
04919 
04920                 pjsua_call_get_info(current_call, &ci);
04921                 printf("Transfer call [%d] %.*s to one of the following:\n",
04922                        current_call,
04923                        (int)ci.remote_info.slen, ci.remote_info.ptr);
04924 
04925                 for (i=0; i<count; ++i) {
04926                     pjsua_call_info call_info;
04927 
04928                     if (ids[i] == call)
04929                         continue;
04930 
04931                     pjsua_call_get_info(ids[i], &call_info);
04932                     printf("%d  %.*s [%.*s]\n",
04933                            ids[i],
04934                            (int)call_info.remote_info.slen,
04935                            call_info.remote_info.ptr,
04936                            (int)call_info.state_text.slen,
04937                            call_info.state_text.ptr);
04938                 }
04939 
04940                 if (!simple_input("Enter call number to be replaced", 
04941                                   buf, sizeof(buf)))
04942                     continue;
04943 
04944                 dst_call = my_atoi(buf);
04945 
04946                 /* Check if call is still there. */
04947 
04948                 if (call != current_call) {
04949                     puts("Call has been disconnected");
04950                     continue;
04951                 }
04952 
04953                 /* Check that destination call is valid. */
04954                 if (dst_call == call) {
04955                     puts("Destination call number must not be the same "
04956                          "as the call being transfered");
04957                     continue;
04958                 }
04959                 if (dst_call >= PJSUA_MAX_CALLS) {
04960                     puts("Invalid destination call number");
04961                     continue;
04962                 }
04963                 if (!pjsua_call_is_active(dst_call)) {
04964                     puts("Invalid destination call number");
04965                     continue;
04966                 }
04967 
04968                 pjsua_msg_data_init(&msg_data);
04969                 if (app_config.no_refersub) {
04970                     /* Add Refer-Sub: false in outgoing REFER request */
04971                     pjsip_generic_string_hdr_init2(&refer_sub, &STR_REFER_SUB,
04972                                                    &STR_FALSE);
04973                     pj_list_push_back(&msg_data.hdr_list, &refer_sub);
04974                 }
04975 
04976                 pjsua_call_xfer_replaces(call, dst_call, 
04977                                          PJSUA_XFER_NO_REQUIRE_REPLACES, 
04978                                          &msg_data);
04979             }
04980             break;
04981 
04982         case '#':
04983             /*
04984              * Send DTMF strings.
04985              */
04986             if (current_call == -1) {
04987                 
04988                 PJ_LOG(3,(THIS_FILE, "No current call"));
04989 
04990             } else if (!pjsua_call_has_media(current_call)) {
04991 
04992                 PJ_LOG(3,(THIS_FILE, "Media is not established yet!"));
04993 
04994             } else {
04995                 pj_str_t digits;
04996                 int call = current_call;
04997                 pj_status_t status;
04998 
04999                 if (!simple_input("DTMF strings to send (0-9*#A-B)", buf, 
05000                                   sizeof(buf)))
05001                 {
05002                         break;
05003                 }
05004 
05005                 if (call != current_call) {
05006                     puts("Call has been disconnected");
05007                     continue;
05008                 }
05009 
05010                 digits = pj_str(buf);
05011                 status = pjsua_call_dial_dtmf(current_call, &digits);
05012                 if (status != PJ_SUCCESS) {
05013                     pjsua_perror(THIS_FILE, "Unable to send DTMF", status);
05014                 } else {
05015                     puts("DTMF digits enqueued for transmission");
05016                 }
05017             }
05018             break;
05019 
05020         case '*':
05021             /* Send DTMF with INFO */
05022             if (current_call == -1) {
05023                 
05024                 PJ_LOG(3,(THIS_FILE, "No current call"));
05025 
05026             } else {
05027                 const pj_str_t SIP_INFO = pj_str("INFO");
05028                 pj_str_t digits;
05029                 int call = current_call;
05030                 int i;
05031                 pj_status_t status;
05032 
05033                 if (!simple_input("DTMF strings to send (0-9*#A-B)", buf, 
05034                                   sizeof(buf)))
05035                 {
05036                         break;
05037                 }
05038 
05039                 if (call != current_call) {
05040                     puts("Call has been disconnected");
05041                     continue;
05042                 }
05043 
05044                 digits = pj_str(buf);
05045                 for (i=0; i<digits.slen; ++i) {
05046                     char body[80];
05047 
05048                     pjsua_msg_data_init(&msg_data);
05049                     msg_data.content_type = pj_str("application/dtmf-relay");
05050                     
05051                     pj_ansi_snprintf(body, sizeof(body),
05052                                      "Signal=%c\r\n"
05053                                      "Duration=160",
05054                                      buf[i]);
05055                     msg_data.msg_body = pj_str(body);
05056 
05057                     status = pjsua_call_send_request(current_call, &SIP_INFO, 
05058                                                      &msg_data);
05059                     if (status != PJ_SUCCESS) {
05060                         break;
05061                     }
05062                 }
05063             }
05064             break;
05065 
05066         case 'S':
05067             /*
05068              * Send arbitrary request
05069              */
05070             if (pjsua_acc_get_count() == 0) {
05071                 puts("Sorry, need at least one account configured");
05072                 break;
05073             }
05074 
05075             puts("Send arbitrary request to remote host");
05076 
05077             /* Input METHOD */
05078             if (!simple_input("Request method:",text,sizeof(text)))
05079                 break;
05080 
05081             /* Input destination URI */
05082             uri = NULL;
05083             ui_input_url("Destination URI", buf, sizeof(buf), &result);
05084             if (result.nb_result != NO_NB) {
05085 
05086                 if (result.nb_result == -1) {
05087                     puts("Sorry you can't do that!");
05088                     continue;
05089                 } else if (result.nb_result == 0) {
05090                     uri = NULL;
05091                     if (current_call == PJSUA_INVALID_ID) {
05092                         puts("No current call");
05093                         continue;
05094                     }
05095                 } else {
05096                     pjsua_buddy_info binfo;
05097                     pjsua_buddy_get_info(result.nb_result-1, &binfo);
05098                     tmp.ptr = buf;
05099                     pj_strncpy_with_null(&tmp, &binfo.uri, sizeof(buf));
05100                     uri = buf;
05101                 }
05102 
05103             } else if (result.uri_result) {
05104                 uri = result.uri_result;
05105             } else {
05106                 continue;
05107             }
05108             
05109             if (uri) {
05110                 tmp = pj_str(uri);
05111                 send_request(text, &tmp);
05112             } else {
05113                 /* If you send call control request using this method
05114                  * (such requests includes BYE, CANCEL, etc.), it will
05115                  * not go well with the call state, so don't do it
05116                  * unless it's for testing.
05117                  */
05118                 pj_str_t method = pj_str(text);
05119                 pjsua_call_send_request(current_call, &method, NULL);
05120             }
05121             break;
05122 
05123         case 'e':
05124             if (pj_ansi_strnicmp(menuin, "echo", 4)==0) {
05125                 pj_str_t tmp;
05126 
05127                 tmp.ptr = menuin+5;
05128                 tmp.slen = pj_ansi_strlen(menuin)-6;
05129 
05130                 if (tmp.slen < 1) {
05131                     puts("Usage: echo [0|1]");
05132                     break;
05133                 }
05134 
05135                 cmd_echo = *tmp.ptr != '0' || tmp.slen!=1;
05136             }
05137             break;
05138 
05139         case 's':
05140             if (pj_ansi_strnicmp(menuin, "sleep", 5)==0) {
05141                 pj_str_t tmp;
05142                 int delay;
05143 
05144                 tmp.ptr = menuin+6;
05145                 tmp.slen = pj_ansi_strlen(menuin)-7;
05146 
05147                 if (tmp.slen < 1) {
05148                     puts("Usage: sleep MSEC");
05149                     break;
05150                 }
05151 
05152                 delay = pj_strtoul(&tmp);
05153                 if (delay < 0) delay = 0;
05154                 pj_thread_sleep(delay);
05155                 break;
05156             }
05157             /* Continue below */
05158 
05159         case 'u':
05160             /*
05161              * Subscribe/unsubscribe presence.
05162              */
05163             ui_input_url("(un)Subscribe presence of", buf, sizeof(buf), &result);
05164             if (result.nb_result != NO_NB) {
05165                 if (result.nb_result == -1) {
05166                     int i, count;
05167                     count = pjsua_get_buddy_count();
05168                     for (i=0; i<count; ++i)
05169                         pjsua_buddy_subscribe_pres(i, menuin[0]=='s');
05170                 } else if (result.nb_result == 0) {
05171                     puts("Sorry, can only subscribe to buddy's presence, "
05172                          "not from existing call");
05173                 } else {
05174                     pjsua_buddy_subscribe_pres(result.nb_result-1, (menuin[0]=='s'));
05175                 }
05176 
05177             } else if (result.uri_result) {
05178                 puts("Sorry, can only subscribe to buddy's presence, "
05179                      "not arbitrary URL (for now)");
05180             }
05181 
05182             break;
05183 
05184         case 'r':
05185             switch (menuin[1]) {
05186             case 'r':
05187                 /*
05188                  * Re-Register.
05189                  */
05190                 pjsua_acc_set_registration(current_acc, PJ_TRUE);
05191                 break;
05192             case 'u':
05193                 /*
05194                  * Unregister
05195                  */
05196                 pjsua_acc_set_registration(current_acc, PJ_FALSE);
05197                 break;
05198             }
05199             break;
05200             
05201         case 't':
05202             pjsua_acc_get_info(current_acc, &acc_info);
05203             acc_info.online_status = !acc_info.online_status;
05204             pjsua_acc_set_online_status(current_acc, acc_info.online_status);
05205             printf("Setting %s online status to %s\n",
05206                    acc_info.acc_uri.ptr,
05207                    (acc_info.online_status?"online":"offline"));
05208             break;
05209 
05210         case 'T':
05211             change_online_status();
05212             break;
05213 
05214         case 'c':
05215             switch (menuin[1]) {
05216             case 'l':
05217                 conf_list();
05218                 break;
05219             case 'c':
05220             case 'd':
05221                 {
05222                     char tmp[10], src_port[10], dst_port[10];
05223                     pj_status_t status;
05224                     int cnt;
05225                     const char *src_title, *dst_title;
05226 
05227                     cnt = sscanf(menuin, "%s %s %s", tmp, src_port, dst_port);
05228 
05229                     if (cnt != 3) {
05230                         conf_list();
05231 
05232                         src_title = (menuin[1]=='c'?
05233                                      "Connect src port #":
05234                                      "Disconnect src port #");
05235                         dst_title = (menuin[1]=='c'?
05236                                      "To dst port #":
05237                                      "From dst port #");
05238 
05239                         if (!simple_input(src_title, src_port, sizeof(src_port)))
05240                             break;
05241 
05242                         if (!simple_input(dst_title, dst_port, sizeof(dst_port)))
05243                             break;
05244                     }
05245 
05246                     if (menuin[1]=='c') {
05247                         status = pjsua_conf_connect(my_atoi(src_port), 
05248                                                     my_atoi(dst_port));
05249                     } else {
05250                         status = pjsua_conf_disconnect(my_atoi(src_port), 
05251                                                        my_atoi(dst_port));
05252                     }
05253                     if (status == PJ_SUCCESS) {
05254                         puts("Success");
05255                     } else {
05256                         puts("ERROR!!");
05257                     }
05258                 }
05259                 break;
05260             }
05261             break;
05262 
05263         case 'V':
05264             /* Adjust audio volume */
05265             sprintf(buf, "Adjust mic level: [%4.1fx] ", app_config.mic_level);
05266             if (simple_input(buf,text,sizeof(text))) {
05267                 char *err;
05268                 app_config.mic_level = (float)strtod(text, &err);
05269                 pjsua_conf_adjust_rx_level(0, app_config.mic_level);
05270             }
05271             sprintf(buf, "Adjust speaker level: [%4.1fx] ", 
05272                     app_config.speaker_level);
05273             if (simple_input(buf,text,sizeof(text))) {
05274                 char *err;
05275                 app_config.speaker_level = (float)strtod(text, &err);
05276                 pjsua_conf_adjust_tx_level(0, app_config.speaker_level);
05277             }
05278 
05279             break;
05280 
05281         case 'd':
05282             if (menuin[1] == 'c') {
05283                 char settings[2000];
05284                 int len;
05285 
05286                 len = write_settings(&app_config, settings, sizeof(settings));
05287                 if (len < 1)
05288                     PJ_LOG(1,(THIS_FILE, "Error: not enough buffer"));
05289                 else
05290                     PJ_LOG(3,(THIS_FILE, 
05291                               "Dumping configuration (%d bytes):\n%s\n",
05292                               len, settings));
05293 
05294             } else if (menuin[1] == 'q') {
05295 
05296                 if (current_call != PJSUA_INVALID_ID) {
05297                     log_call_dump(current_call);
05298                 } else {
05299                     PJ_LOG(3,(THIS_FILE, "No current call"));
05300                 }
05301 
05302             } else {
05303                 app_dump(menuin[1]=='d');
05304             }
05305             break;
05306 
05307 
05308         case 'f':
05309             if (simple_input("Enter output filename", buf, sizeof(buf))) {
05310                 char settings[2000];
05311                 int len;
05312 
05313                 len = write_settings(&app_config, settings, sizeof(settings));
05314                 if (len < 1)
05315                     PJ_LOG(1,(THIS_FILE, "Error: not enough buffer"));
05316                 else {
05317                     pj_oshandle_t fd;
05318                     pj_status_t status;
05319 
05320                     status = pj_file_open(app_config.pool, buf, 
05321                                           PJ_O_WRONLY, &fd);
05322                     if (status != PJ_SUCCESS) {
05323                         pjsua_perror(THIS_FILE, "Unable to open file", status);
05324                     } else {
05325                         pj_ssize_t size = len;
05326                         pj_file_write(fd, settings, &size);
05327                         pj_file_close(fd);
05328 
05329                         printf("Settings successfully written to '%s'\n", buf);
05330                     }
05331                 }
05332                 
05333             }
05334             break;
05335 
05336 
05337         case 'L':   /* Restart */
05338             app_restart = PJ_TRUE;
05339             /* Continues below */
05340 
05341         case 'q':
05342             goto on_exit;
05343 
05344         case 'R':
05345             if (!pjsua_call_is_active(current_call)) {
05346                 PJ_LOG(1,(THIS_FILE, "Call %d has gone", current_call));
05347             } else if (menuin[1] == 'a') {
05348                 pjsua_call_process_redirect(current_call, 
05349                                             PJSIP_REDIRECT_ACCEPT);
05350             } else if (menuin[1] == 'r') {
05351                 pjsua_call_process_redirect(current_call,
05352                                             PJSIP_REDIRECT_REJECT);
05353             } else {
05354                 pjsua_call_process_redirect(current_call,
05355                                             PJSIP_REDIRECT_STOP);
05356             }
05357             break;
05358 
05359         default:
05360             if (menuin[0] != '\n' && menuin[0] != '\r') {
05361                 printf("Invalid input %s", menuin);
05362             }
05363             keystroke_help();
05364             break;
05365         }
05366     }
05367 
05368 on_exit:
05369     ;
05370 }
05371 
05372 /*
05373  * A simple registrar, invoked by default_mod_on_rx_request()
05374  */
05375 static void simple_registrar(pjsip_rx_data *rdata)
05376 {
05377     pjsip_tx_data *tdata;
05378     const pjsip_expires_hdr *exp;
05379     const pjsip_hdr *h;
05380     unsigned cnt = 0;
05381     pjsip_generic_string_hdr *srv;
05382     pj_status_t status;
05383 
05384     status = pjsip_endpt_create_response(pjsua_get_pjsip_endpt(),
05385                                  rdata, 200, NULL, &tdata);
05386     if (status != PJ_SUCCESS)
05387     return;
05388 
05389     exp = pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_EXPIRES, NULL);
05390 
05391     h = rdata->msg_info.msg->hdr.next;
05392     while (h != &rdata->msg_info.msg->hdr) {
05393     if (h->type == PJSIP_H_CONTACT) {
05394     const pjsip_contact_hdr *c = (const pjsip_contact_hdr*)h;
05395     int e = c->expires;
05396 
05397     if (e < 0) {
05398         if (exp)
05399             e = exp->ivalue;
05400         else
05401             e = 3600;
05402     }
05403 
05404     if (e > 0) {
05405         pjsip_contact_hdr *nc = pjsip_hdr_clone(tdata->pool, h);
05406         nc->expires = e;
05407         pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)nc);
05408         ++cnt;
05409     }
05410     }
05411     h = h->next;
05412     }
05413 
05414     srv = pjsip_generic_string_hdr_create(tdata->pool, NULL, NULL);
05415     srv->name = pj_str("Server");
05416     srv->hvalue = pj_str("pjsua simple registrar");
05417     pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)srv);
05418 
05419     pjsip_endpt_send_response2(pjsua_get_pjsip_endpt(),
05420                        rdata, tdata, NULL, NULL);
05421 }
05422 
05423 
05424 
05425 /*****************************************************************************
05426  * A simple module to handle otherwise unhandled request. We will register
05427  * this with the lowest priority.
05428  */
05429 
05430 /* Notification on incoming request */
05431 static pj_bool_t default_mod_on_rx_request(pjsip_rx_data *rdata)
05432 {
05433     pjsip_tx_data *tdata;
05434     pjsip_status_code status_code;
05435     pj_status_t status;
05436 
05437     /* Don't respond to ACK! */
05438     if (pjsip_method_cmp(&rdata->msg_info.msg->line.req.method,
05439                          &pjsip_ack_method) == 0)
05440         return PJ_TRUE;
05441 
05442     /* Simple registrar */
05443     if (pjsip_method_cmp(&rdata->msg_info.msg->line.req.method,
05444                          &pjsip_register_method) == 0)
05445     {
05446         simple_registrar(rdata);
05447         return PJ_TRUE;
05448     }
05449 
05450     /* Create basic response. */
05451     if (pjsip_method_cmp(&rdata->msg_info.msg->line.req.method, 
05452                          &pjsip_notify_method) == 0)
05453     {
05454         /* Unsolicited NOTIFY's, send with Bad Request */
05455         status_code = PJSIP_SC_BAD_REQUEST;
05456     } else {
05457         /* Probably unknown method */
05458         status_code = PJSIP_SC_METHOD_NOT_ALLOWED;
05459     }
05460     status = pjsip_endpt_create_response(pjsua_get_pjsip_endpt(), 
05461                                          rdata, status_code, 
05462                                          NULL, &tdata);
05463     if (status != PJ_SUCCESS) {
05464         pjsua_perror(THIS_FILE, "Unable to create response", status);
05465         return PJ_TRUE;
05466     }
05467 
05468     /* Add Allow if we're responding with 405 */
05469     if (status_code == PJSIP_SC_METHOD_NOT_ALLOWED) {
05470         const pjsip_hdr *cap_hdr;
05471         cap_hdr = pjsip_endpt_get_capability(pjsua_get_pjsip_endpt(), 
05472                                              PJSIP_H_ALLOW, NULL);
05473         if (cap_hdr) {
05474             pjsip_msg_add_hdr(tdata->msg, pjsip_hdr_clone(tdata->pool, 
05475                                                            cap_hdr));
05476         }
05477     }
05478 
05479     /* Add User-Agent header */
05480     {
05481         pj_str_t user_agent;
05482         char tmp[80];
05483         const pj_str_t USER_AGENT = { "User-Agent", 10};
05484         pjsip_hdr *h;
05485 
05486         pj_ansi_snprintf(tmp, sizeof(tmp), "PJSUA v%s/%s", 
05487                          pj_get_version(), PJ_OS_NAME);
05488         pj_strdup2_with_null(tdata->pool, &user_agent, tmp);
05489 
05490         h = (pjsip_hdr*) pjsip_generic_string_hdr_create(tdata->pool,
05491                                                          &USER_AGENT,
05492                                                          &user_agent);
05493         pjsip_msg_add_hdr(tdata->msg, h);
05494     }
05495 
05496     pjsip_endpt_send_response2(pjsua_get_pjsip_endpt(), rdata, tdata, 
05497                                NULL, NULL);
05498 
05499     return PJ_TRUE;
05500 }
05501 
05502 
05503 /* The module instance. */
05504 static pjsip_module mod_default_handler = 
05505 {
05506     NULL, NULL,                         /* prev, next.          */
05507     { "mod-default-handler", 19 },      /* Name.                */
05508     -1,                                 /* Id                   */
05509     PJSIP_MOD_PRIORITY_APPLICATION+99,  /* Priority             */
05510     NULL,                               /* load()               */
05511     NULL,                               /* start()              */
05512     NULL,                               /* stop()               */
05513     NULL,                               /* unload()             */
05514     &default_mod_on_rx_request,         /* on_rx_request()      */
05515     NULL,                               /* on_rx_response()     */
05516     NULL,                               /* on_tx_request.       */
05517     NULL,                               /* on_tx_response()     */
05518     NULL,                               /* on_tsx_state()       */
05519 
05520 };
05521 
05522 
05523 
05524 
05525 /*****************************************************************************
05526  * Public API
05527  */
05528 
05529 pj_status_t app_init(int argc, char *argv[])
05530 {
05531     pjsua_transport_id transport_id = -1;
05532     pjsua_transport_config tcp_cfg;
05533     unsigned i;
05534     pj_status_t status;
05535 
05536     app_restart = PJ_FALSE;
05537 
05538     /* Create pjsua */
05539     status = pjsua_create();
05540     if (status != PJ_SUCCESS)
05541         return status;
05542 
05543     /* Create pool for application */
05544     app_config.pool = pjsua_pool_create("pjsua-app", 1000, 1000);
05545 
05546     /* Initialize default config */
05547     default_config(&app_config);
05548     
05549     /* Parse the arguments */
05550     status = parse_args(argc, argv, &app_config, &uri_arg);
05551     if (status != PJ_SUCCESS)
05552         return status;
05553 
05554     /* Initialize application callbacks */
05555     app_config.cfg.cb.on_call_state = &on_call_state;
05556     app_config.cfg.cb.on_call_media_state = &on_call_media_state;
05557     app_config.cfg.cb.on_incoming_call = &on_incoming_call;
05558     app_config.cfg.cb.on_call_tsx_state = &on_call_tsx_state;
05559     app_config.cfg.cb.on_dtmf_digit = &call_on_dtmf_callback;
05560     app_config.cfg.cb.on_call_redirected = &call_on_redirected;
05561     app_config.cfg.cb.on_reg_state = &on_reg_state;
05562     app_config.cfg.cb.on_incoming_subscribe = &on_incoming_subscribe;
05563     app_config.cfg.cb.on_buddy_state = &on_buddy_state;
05564     app_config.cfg.cb.on_buddy_evsub_state = &on_buddy_evsub_state;
05565     app_config.cfg.cb.on_pager = &on_pager;
05566     app_config.cfg.cb.on_typing = &on_typing;
05567     app_config.cfg.cb.on_call_transfer_status = &on_call_transfer_status;
05568     app_config.cfg.cb.on_call_replaced = &on_call_replaced;
05569     app_config.cfg.cb.on_nat_detect = &on_nat_detect;
05570     app_config.cfg.cb.on_mwi_info = &on_mwi_info;
05571     app_config.cfg.cb.on_transport_state = &on_transport_state;
05572     app_config.cfg.cb.on_ice_transport_error = &on_ice_transport_error;
05573     app_config.cfg.cb.on_snd_dev_operation = &on_snd_dev_operation;
05574     app_config.cfg.cb.on_call_media_event = &on_call_media_event;
05575 #ifdef TRANSPORT_ADAPTER_SAMPLE
05576     app_config.cfg.cb.on_create_media_transport = &on_create_media_transport;
05577 #endif
05578     app_config.log_cfg.cb = log_cb;
05579 
05580     /* Set sound device latency */
05581     if (app_config.capture_lat > 0)
05582         app_config.media_cfg.snd_rec_latency = app_config.capture_lat;
05583     if (app_config.playback_lat)
05584         app_config.media_cfg.snd_play_latency = app_config.playback_lat;
05585 
05586     /* Initialize pjsua */
05587     status = pjsua_init(&app_config.cfg, &app_config.log_cfg,
05588                         &app_config.media_cfg);
05589     if (status != PJ_SUCCESS)
05590         return status;
05591 
05592     /* Initialize our module to handle otherwise unhandled request */
05593     status = pjsip_endpt_register_module(pjsua_get_pjsip_endpt(),
05594                                          &mod_default_handler);
05595     if (status != PJ_SUCCESS)
05596         return status;
05597 
05598 #ifdef STEREO_DEMO
05599     stereo_demo();
05600 #endif
05601 
05602     /* Initialize calls data */
05603     for (i=0; i<PJ_ARRAY_SIZE(app_config.call_data); ++i) {
05604         app_config.call_data[i].timer.id = PJSUA_INVALID_ID;
05605         app_config.call_data[i].timer.cb = &call_timeout_callback;
05606     }
05607 
05608     /* Optionally registers WAV file */
05609     for (i=0; i<app_config.wav_count; ++i) {
05610         pjsua_player_id wav_id;
05611         unsigned play_options = 0;
05612 
05613         if (app_config.auto_play_hangup)
05614             play_options |= PJMEDIA_FILE_NO_LOOP;
05615 
05616         status = pjsua_player_create(&app_config.wav_files[i], play_options, 
05617                                      &wav_id);
05618         if (status != PJ_SUCCESS)
05619             goto on_error;
05620 
05621         if (app_config.wav_id == PJSUA_INVALID_ID) {
05622             app_config.wav_id = wav_id;
05623             app_config.wav_port = pjsua_player_get_conf_port(app_config.wav_id);
05624             if (app_config.auto_play_hangup) {
05625                 pjmedia_port *port;
05626 
05627                 pjsua_player_get_port(app_config.wav_id, &port);
05628                 status = pjmedia_wav_player_set_eof_cb(port, NULL, 
05629                                                        &on_playfile_done);
05630                 if (status != PJ_SUCCESS)
05631                     goto on_error;
05632 
05633                 pj_timer_entry_init(&app_config.auto_hangup_timer, 0, NULL, 
05634                                     &hangup_timeout_callback);
05635             }
05636         }
05637     }
05638 
05639     /* Optionally registers tone players */
05640     for (i=0; i<app_config.tone_count; ++i) {
05641         pjmedia_port *tport;
05642         char name[80];
05643         pj_str_t label;
05644         pj_status_t status;
05645 
05646         pj_ansi_snprintf(name, sizeof(name), "tone-%d,%d",
05647                          app_config.tones[i].freq1, 
05648                          app_config.tones[i].freq2);
05649         label = pj_str(name);
05650         status = pjmedia_tonegen_create2(app_config.pool, &label,
05651                                          8000, 1, 160, 16, 
05652                                          PJMEDIA_TONEGEN_LOOP,  &tport);
05653         if (status != PJ_SUCCESS) {
05654             pjsua_perror(THIS_FILE, "Unable to create tone generator", status);
05655             goto on_error;
05656         }
05657 
05658         status = pjsua_conf_add_port(app_config.pool, tport, 
05659                                      &app_config.tone_slots[i]);
05660         pj_assert(status == PJ_SUCCESS);
05661 
05662         status = pjmedia_tonegen_play(tport, 1, &app_config.tones[i], 0);
05663         pj_assert(status == PJ_SUCCESS);
05664     }
05665 
05666     /* Optionally create recorder file, if any. */
05667     if (app_config.rec_file.slen) {
05668         status = pjsua_recorder_create(&app_config.rec_file, 0, NULL, 0, 0,
05669                                        &app_config.rec_id);
05670         if (status != PJ_SUCCESS)
05671             goto on_error;
05672 
05673         app_config.rec_port = pjsua_recorder_get_conf_port(app_config.rec_id);
05674     }
05675 
05676     pj_memcpy(&tcp_cfg, &app_config.udp_cfg, sizeof(tcp_cfg));
05677 
05678     /* Create ringback tones */
05679     if (app_config.no_tones == PJ_FALSE) {
05680         unsigned i, samples_per_frame;
05681         pjmedia_tone_desc tone[RING_CNT+RINGBACK_CNT];
05682         pj_str_t name;
05683 
05684         samples_per_frame = app_config.media_cfg.audio_frame_ptime * 
05685                             app_config.media_cfg.clock_rate *
05686                             app_config.media_cfg.channel_count / 1000;
05687 
05688         /* Ringback tone (call is ringing) */
05689         name = pj_str("ringback");
05690         status = pjmedia_tonegen_create2(app_config.pool, &name, 
05691                                          app_config.media_cfg.clock_rate,
05692                                          app_config.media_cfg.channel_count, 
05693                                          samples_per_frame,
05694                                          16, PJMEDIA_TONEGEN_LOOP, 
05695                                          &app_config.ringback_port);
05696         if (status != PJ_SUCCESS)
05697             goto on_error;
05698 
05699         pj_bzero(&tone, sizeof(tone));
05700         for (i=0; i<RINGBACK_CNT; ++i) {
05701             tone[i].freq1 = RINGBACK_FREQ1;
05702             tone[i].freq2 = RINGBACK_FREQ2;
05703             tone[i].on_msec = RINGBACK_ON;
05704             tone[i].off_msec = RINGBACK_OFF;
05705         }
05706         tone[RINGBACK_CNT-1].off_msec = RINGBACK_INTERVAL;
05707 
05708         pjmedia_tonegen_play(app_config.ringback_port, RINGBACK_CNT, tone,
05709                              PJMEDIA_TONEGEN_LOOP);
05710 
05711 
05712         status = pjsua_conf_add_port(app_config.pool, app_config.ringback_port,
05713                                      &app_config.ringback_slot);
05714         if (status != PJ_SUCCESS)
05715             goto on_error;
05716 
05717         /* Ring (to alert incoming call) */
05718         name = pj_str("ring");
05719         status = pjmedia_tonegen_create2(app_config.pool, &name, 
05720                                          app_config.media_cfg.clock_rate,
05721                                          app_config.media_cfg.channel_count, 
05722                                          samples_per_frame,
05723                                          16, PJMEDIA_TONEGEN_LOOP, 
05724                                          &app_config.ring_port);
05725         if (status != PJ_SUCCESS)
05726             goto on_error;
05727 
05728         for (i=0; i<RING_CNT; ++i) {
05729             tone[i].freq1 = RING_FREQ1;
05730             tone[i].freq2 = RING_FREQ2;
05731             tone[i].on_msec = RING_ON;
05732             tone[i].off_msec = RING_OFF;
05733         }
05734         tone[RING_CNT-1].off_msec = RING_INTERVAL;
05735 
05736         pjmedia_tonegen_play(app_config.ring_port, RING_CNT, 
05737                              tone, PJMEDIA_TONEGEN_LOOP);
05738 
05739         status = pjsua_conf_add_port(app_config.pool, app_config.ring_port,
05740                                      &app_config.ring_slot);
05741         if (status != PJ_SUCCESS)
05742             goto on_error;
05743 
05744     }
05745 
05746     /* Create AVI player virtual devices */
05747     if (app_config.avi_cnt) {
05748 #if PJMEDIA_HAS_VIDEO && PJMEDIA_VIDEO_DEV_HAS_AVI
05749         pjmedia_vid_dev_factory *avi_factory;
05750 
05751         status = pjmedia_avi_dev_create_factory(pjsua_get_pool_factory(),
05752                                                 app_config.avi_cnt,
05753                                                 &avi_factory);
05754         if (status != PJ_SUCCESS) {
05755             PJ_PERROR(1,(THIS_FILE, status, "Error creating AVI factory"));
05756             goto on_error;
05757         }
05758 
05759         for (i=0; i<app_config.avi_cnt; ++i) {
05760             pjmedia_avi_dev_param avdp;
05761             pjmedia_vid_dev_index avid;
05762             unsigned strm_idx, strm_cnt;
05763 
05764             app_config.avi[i].dev_id = PJMEDIA_VID_INVALID_DEV;
05765             app_config.avi[i].slot = PJSUA_INVALID_ID;
05766 
05767             pjmedia_avi_dev_param_default(&avdp);
05768             avdp.path = app_config.avi[i].path;
05769 
05770             status =  pjmedia_avi_dev_alloc(avi_factory, &avdp, &avid);
05771             if (status != PJ_SUCCESS) {
05772                 PJ_PERROR(1,(THIS_FILE, status,
05773                              "Error creating AVI player for %.*s",
05774                              (int)avdp.path.slen, avdp.path.ptr));
05775                 goto on_error;
05776             }
05777 
05778             PJ_LOG(4,(THIS_FILE, "AVI player %.*s created, dev_id=%d",
05779                       (int)avdp.title.slen, avdp.title.ptr, avid));
05780 
05781             app_config.avi[i].dev_id = avid;
05782             if (app_config.avi_def_idx == PJSUA_INVALID_ID)
05783                 app_config.avi_def_idx = i;
05784 
05785             strm_cnt = pjmedia_avi_streams_get_num_streams(avdp.avi_streams);
05786             for (strm_idx=0; strm_idx<strm_cnt; ++strm_idx) {
05787                 pjmedia_port *aud;
05788                 pjmedia_format *fmt;
05789                 pjsua_conf_port_id slot;
05790                 char fmt_name[5];
05791 
05792                 aud = pjmedia_avi_streams_get_stream(avdp.avi_streams,
05793                                                      strm_idx);
05794                 fmt = &aud->info.fmt;
05795 
05796                 pjmedia_fourcc_name(fmt->id, fmt_name);
05797 
05798                 if (fmt->id == PJMEDIA_FORMAT_PCM) {
05799                     status = pjsua_conf_add_port(app_config.pool, aud,
05800                                                  &slot);
05801                     if (status == PJ_SUCCESS) {
05802                         PJ_LOG(4,(THIS_FILE,
05803                                   "AVI %.*s: audio added to slot %d",
05804                                   (int)avdp.title.slen, avdp.title.ptr,
05805                                   slot));
05806                         app_config.avi[i].slot = slot;
05807                     }
05808                 } else {
05809                     PJ_LOG(4,(THIS_FILE,
05810                               "AVI %.*s: audio ignored, format=%s",
05811                               (int)avdp.title.slen, avdp.title.ptr,
05812                               fmt_name));
05813                 }
05814             }
05815         }
05816 #else
05817         PJ_LOG(2,(THIS_FILE,
05818                   "Warning: --play-avi is ignored because AVI is disabled"));
05819 #endif  /* PJMEDIA_VIDEO_DEV_HAS_AVI */
05820     }
05821 
05822     /* Add UDP transport unless it's disabled. */
05823     if (!app_config.no_udp) {
05824         pjsua_acc_id aid;
05825         pjsip_transport_type_e type = PJSIP_TRANSPORT_UDP;
05826 
05827         status = pjsua_transport_create(type,
05828                                         &app_config.udp_cfg,
05829                                         &transport_id);
05830         if (status != PJ_SUCCESS)
05831             goto on_error;
05832 
05833         /* Add local account */
05834         pjsua_acc_add_local(transport_id, PJ_TRUE, &aid);
05835         if (PJMEDIA_HAS_VIDEO) {
05836             pjsua_acc_config acc_cfg;
05837             pjsua_acc_get_config(aid, &acc_cfg);
05838             app_config_init_video(&acc_cfg);
05839             pjsua_acc_modify(aid, &acc_cfg);
05840         }
05841         //pjsua_acc_set_transport(aid, transport_id);
05842         pjsua_acc_set_online_status(current_acc, PJ_TRUE);
05843 
05844         if (app_config.udp_cfg.port == 0) {
05845             pjsua_transport_info ti;
05846             pj_sockaddr_in *a;
05847 
05848             pjsua_transport_get_info(transport_id, &ti);
05849             a = (pj_sockaddr_in*)&ti.local_addr;
05850 
05851             tcp_cfg.port = pj_ntohs(a->sin_port);
05852         }
05853     }
05854 
05855     /* Add UDP IPv6 transport unless it's disabled. */
05856     if (!app_config.no_udp && app_config.ipv6) {
05857         pjsua_acc_id aid;
05858         pjsip_transport_type_e type = PJSIP_TRANSPORT_UDP6;
05859         pjsua_transport_config udp_cfg;
05860 
05861         udp_cfg = app_config.udp_cfg;
05862         if (udp_cfg.port == 0)
05863             udp_cfg.port = 5060;
05864         else
05865             udp_cfg.port += 10;
05866         status = pjsua_transport_create(type,
05867                                         &udp_cfg,
05868                                         &transport_id);
05869         if (status != PJ_SUCCESS)
05870             goto on_error;
05871 
05872         /* Add local account */
05873         pjsua_acc_add_local(transport_id, PJ_TRUE, &aid);
05874         if (PJMEDIA_HAS_VIDEO) {
05875             pjsua_acc_config acc_cfg;
05876             pjsua_acc_get_config(aid, &acc_cfg);
05877             app_config_init_video(&acc_cfg);
05878             pjsua_acc_modify(aid, &acc_cfg);
05879         }
05880         //pjsua_acc_set_transport(aid, transport_id);
05881         pjsua_acc_set_online_status(current_acc, PJ_TRUE);
05882 
05883         if (app_config.udp_cfg.port == 0) {
05884             pjsua_transport_info ti;
05885             pj_sockaddr_in *a;
05886 
05887             pjsua_transport_get_info(transport_id, &ti);
05888             a = (pj_sockaddr_in*)&ti.local_addr;
05889 
05890             tcp_cfg.port = pj_ntohs(a->sin_port);
05891         }
05892     }
05893 
05894     /* Add TCP transport unless it's disabled */
05895     if (!app_config.no_tcp) {
05896         pjsua_acc_id aid;
05897 
05898         status = pjsua_transport_create(PJSIP_TRANSPORT_TCP,
05899                                         &tcp_cfg, 
05900                                         &transport_id);
05901         if (status != PJ_SUCCESS)
05902             goto on_error;
05903 
05904         /* Add local account */
05905         pjsua_acc_add_local(transport_id, PJ_TRUE, &aid);
05906         if (PJMEDIA_HAS_VIDEO) {
05907             pjsua_acc_config acc_cfg;
05908             pjsua_acc_get_config(aid, &acc_cfg);
05909             app_config_init_video(&acc_cfg);
05910             pjsua_acc_modify(aid, &acc_cfg);
05911         }
05912         pjsua_acc_set_online_status(current_acc, PJ_TRUE);
05913 
05914     }
05915 
05916 
05917 #if defined(PJSIP_HAS_TLS_TRANSPORT) && PJSIP_HAS_TLS_TRANSPORT!=0
05918     /* Add TLS transport when application wants one */
05919     if (app_config.use_tls) {
05920 
05921         pjsua_acc_id acc_id;
05922 
05923         /* Copy the QoS settings */
05924         tcp_cfg.tls_setting.qos_type = tcp_cfg.qos_type;
05925         pj_memcpy(&tcp_cfg.tls_setting.qos_params, &tcp_cfg.qos_params, 
05926                   sizeof(tcp_cfg.qos_params));
05927 
05928         /* Set TLS port as TCP port+1 */
05929         tcp_cfg.port++;
05930         status = pjsua_transport_create(PJSIP_TRANSPORT_TLS,
05931                                         &tcp_cfg, 
05932                                         &transport_id);
05933         tcp_cfg.port--;
05934         if (status != PJ_SUCCESS)
05935             goto on_error;
05936         
05937         /* Add local account */
05938         pjsua_acc_add_local(transport_id, PJ_FALSE, &acc_id);
05939         if (PJMEDIA_HAS_VIDEO) {
05940             pjsua_acc_config acc_cfg;
05941             pjsua_acc_get_config(acc_id, &acc_cfg);
05942             app_config_init_video(&acc_cfg);
05943             pjsua_acc_modify(acc_id, &acc_cfg);
05944         }
05945         pjsua_acc_set_online_status(acc_id, PJ_TRUE);
05946     }
05947 #endif
05948 
05949     if (transport_id == -1) {
05950         PJ_LOG(1,(THIS_FILE, "Error: no transport is configured"));
05951         status = -1;
05952         goto on_error;
05953     }
05954 
05955 
05956     /* Add accounts */
05957     for (i=0; i<app_config.acc_cnt; ++i) {
05958         app_config.acc_cfg[i].rtp_cfg = app_config.rtp_cfg;
05959         app_config.acc_cfg[i].reg_retry_interval = 300;
05960         app_config.acc_cfg[i].reg_first_retry_interval = 60;
05961 
05962         app_config_init_video(&app_config.acc_cfg[i]);
05963 
05964         status = pjsua_acc_add(&app_config.acc_cfg[i], PJ_TRUE, NULL);
05965         if (status != PJ_SUCCESS)
05966             goto on_error;
05967         pjsua_acc_set_online_status(current_acc, PJ_TRUE);
05968     }
05969 
05970     /* Add buddies */
05971     for (i=0; i<app_config.buddy_cnt; ++i) {
05972         status = pjsua_buddy_add(&app_config.buddy_cfg[i], NULL);
05973         if (status != PJ_SUCCESS) {
05974             PJ_PERROR(1,(THIS_FILE, status, "Error adding buddy"));
05975             goto on_error;
05976         }
05977     }
05978 
05979     /* Optionally disable some codec */
05980     for (i=0; i<app_config.codec_dis_cnt; ++i) {
05981         pjsua_codec_set_priority(&app_config.codec_dis[i],PJMEDIA_CODEC_PRIO_DISABLED);
05982 #if PJSUA_HAS_VIDEO
05983         pjsua_vid_codec_set_priority(&app_config.codec_dis[i],PJMEDIA_CODEC_PRIO_DISABLED);
05984 #endif
05985     }
05986 
05987     /* Optionally set codec orders */
05988     for (i=0; i<app_config.codec_cnt; ++i) {
05989         pjsua_codec_set_priority(&app_config.codec_arg[i],
05990                                  (pj_uint8_t)(PJMEDIA_CODEC_PRIO_NORMAL+i+9));
05991 #if PJSUA_HAS_VIDEO
05992         pjsua_vid_codec_set_priority(&app_config.codec_arg[i],
05993                                      (pj_uint8_t)(PJMEDIA_CODEC_PRIO_NORMAL+i+9));
05994 #endif
05995     }
05996 
05997     /* Add RTP transports */
05998     if (app_config.ipv6)
05999         status = create_ipv6_media_transports();
06000   #if DISABLED_FOR_TICKET_1185
06001     else
06002         status = pjsua_media_transports_create(&app_config.rtp_cfg);
06003   #endif
06004     if (status != PJ_SUCCESS)
06005         goto on_error;
06006 
06007     /* Use null sound device? */
06008 #ifndef STEREO_DEMO
06009     if (app_config.null_audio) {
06010         status = pjsua_set_null_snd_dev();
06011         if (status != PJ_SUCCESS)
06012             return status;
06013     }
06014 #endif
06015 
06016     if (app_config.capture_dev  != PJSUA_INVALID_ID ||
06017         app_config.playback_dev != PJSUA_INVALID_ID) 
06018     {
06019         status = pjsua_set_snd_dev(app_config.capture_dev, 
06020                                    app_config.playback_dev);
06021         if (status != PJ_SUCCESS)
06022             goto on_error;
06023     }
06024 
06025     return PJ_SUCCESS;
06026 
06027 on_error:
06028     app_destroy();
06029     return status;
06030 }
06031 
06032 
06033 static int stdout_refresh_proc(void *arg)
06034 {
06035     PJ_UNUSED_ARG(arg);
06036 
06037     /* Set thread to lowest priority so that it doesn't clobber
06038      * stdout output
06039      */
06040     pj_thread_set_prio(pj_thread_this(), 
06041                        pj_thread_get_prio_min(pj_thread_this()));
06042 
06043     while (!stdout_refresh_quit) {
06044         pj_thread_sleep(stdout_refresh * 1000);
06045         puts(stdout_refresh_text);
06046         fflush(stdout);
06047     }
06048 
06049     return 0;
06050 }
06051 
06052 pj_status_t app_main(void)
06053 {
06054     pj_thread_t *stdout_refresh_thread = NULL;
06055     pj_status_t status;
06056 
06057     /* Start pjsua */
06058     status = pjsua_start();
06059     if (status != PJ_SUCCESS) {
06060         app_destroy();
06061         return status;
06062     }
06063 
06064     /* Start console refresh thread */
06065     if (stdout_refresh > 0) {
06066         pj_thread_create(app_config.pool, "stdout", &stdout_refresh_proc,
06067                          NULL, 0, 0, &stdout_refresh_thread);
06068     }
06069 
06070     console_app_main(&uri_arg);
06071 
06072     if (stdout_refresh_thread) {
06073         stdout_refresh_quit = PJ_TRUE;
06074         pj_thread_join(stdout_refresh_thread);
06075         pj_thread_destroy(stdout_refresh_thread);
06076     }
06077 
06078     return PJ_SUCCESS;
06079 }
06080 
06081 pj_status_t app_destroy(void)
06082 {
06083     pj_status_t status;
06084     unsigned i;
06085 
06086 #ifdef STEREO_DEMO
06087     if (app_config.snd) {
06088         pjmedia_snd_port_destroy(app_config.snd);
06089         app_config.snd = NULL;
06090     }
06091     if (app_config.sc_ch1) {
06092         pjsua_conf_remove_port(app_config.sc_ch1_slot);
06093         app_config.sc_ch1_slot = PJSUA_INVALID_ID;
06094         pjmedia_port_destroy(app_config.sc_ch1);
06095         app_config.sc_ch1 = NULL;
06096     }
06097     if (app_config.sc) {
06098         pjmedia_port_destroy(app_config.sc);
06099         app_config.sc = NULL;
06100     }
06101 #endif
06102 
06103     /* Close avi devs and ports */
06104     for (i=0; i<app_config.avi_cnt; ++i) {
06105         if (app_config.avi[i].slot != PJSUA_INVALID_ID)
06106             pjsua_conf_remove_port(app_config.avi[i].slot);
06107 #if PJMEDIA_HAS_VIDEO && PJMEDIA_VIDEO_DEV_HAS_AVI
06108         if (app_config.avi[i].dev_id != PJMEDIA_VID_INVALID_DEV)
06109             pjmedia_avi_dev_free(app_config.avi[i].dev_id);
06110 #endif
06111     }
06112 
06113     /* Close ringback port */
06114     if (app_config.ringback_port && 
06115         app_config.ringback_slot != PJSUA_INVALID_ID) 
06116     {
06117         pjsua_conf_remove_port(app_config.ringback_slot);
06118         app_config.ringback_slot = PJSUA_INVALID_ID;
06119         pjmedia_port_destroy(app_config.ringback_port);
06120         app_config.ringback_port = NULL;
06121     }
06122 
06123     /* Close ring port */
06124     if (app_config.ring_port && app_config.ring_slot != PJSUA_INVALID_ID) {
06125         pjsua_conf_remove_port(app_config.ring_slot);
06126         app_config.ring_slot = PJSUA_INVALID_ID;
06127         pjmedia_port_destroy(app_config.ring_port);
06128         app_config.ring_port = NULL;
06129     }
06130 
06131     /* Close tone generators */
06132     for (i=0; i<app_config.tone_count; ++i) {
06133         pjsua_conf_remove_port(app_config.tone_slots[i]);
06134     }
06135 
06136     if (app_config.pool) {
06137         pj_pool_release(app_config.pool);
06138         app_config.pool = NULL;
06139     }
06140 
06141     status = pjsua_destroy();
06142 
06143     pj_bzero(&app_config, sizeof(app_config));
06144 
06145     return status;
06146 }
06147 
06148 
06149 #ifdef STEREO_DEMO
06150 /*
06151  * In this stereo demo, we open the sound device in stereo mode and
06152  * arrange the attachment to the PJSUA-LIB conference bridge as such
06153  * so that channel0/left channel of the sound device corresponds to
06154  * slot 0 in the bridge, and channel1/right channel of the sound
06155  * device corresponds to slot 1 in the bridge. Then user can independently
06156  * feed different media to/from the speakers/microphones channels, by
06157  * connecting them to slot 0 or 1 respectively.
06158  *
06159  * Here's how the connection looks like:
06160  *
06161    +-----------+ stereo +-----------------+ 2x mono +-----------+
06162    | AUDIO DEV |<------>| SPLITCOMB   left|<------->|#0  BRIDGE |
06163    +-----------+        |            right|<------->|#1         |
06164                         +-----------------+         +-----------+
06165  */
06166 static void stereo_demo()
06167 {
06168     pjmedia_port *conf;
06169     pj_status_t status;
06170 
06171     /* Disable existing sound device */
06172     conf = pjsua_set_no_snd_dev();
06173 
06174     /* Create stereo-mono splitter/combiner */
06175     status = pjmedia_splitcomb_create(app_config.pool, 
06176                                       conf->info.clock_rate /* clock rate */,
06177                                       2     /* stereo */,
06178                                       2 * conf->info.samples_per_frame,
06179                                       conf->info.bits_per_sample,
06180                                       0     /* options */,
06181                                       &app_config.sc);
06182     pj_assert(status == PJ_SUCCESS);
06183 
06184     /* Connect channel0 (left channel?) to conference port slot0 */
06185     status = pjmedia_splitcomb_set_channel(app_config.sc, 0 /* ch0 */, 
06186                                            0 /*options*/,
06187                                            conf);
06188     pj_assert(status == PJ_SUCCESS);
06189 
06190     /* Create reverse channel for channel1 (right channel?)... */
06191     status = pjmedia_splitcomb_create_rev_channel(app_config.pool,
06192                                                   app_config.sc,
06193                                                   1  /* ch1 */,
06194                                                   0  /* options */,
06195                                                   &app_config.sc_ch1);
06196     pj_assert(status == PJ_SUCCESS);
06197 
06198     /* .. and register it to conference bridge (it would be slot1
06199      * if there's no other devices connected to the bridge)
06200      */
06201     status = pjsua_conf_add_port(app_config.pool, app_config.sc_ch1, 
06202                                  &app_config.sc_ch1_slot);
06203     pj_assert(status == PJ_SUCCESS);
06204     
06205     /* Create sound device */
06206     status = pjmedia_snd_port_create(app_config.pool, -1, -1, 
06207                                      conf->info.clock_rate,
06208                                      2      /* stereo */,
06209                                      2 * conf->info.samples_per_frame,
06210                                      conf->info.bits_per_sample,
06211                                      0, &app_config.snd);
06212     pj_assert(status == PJ_SUCCESS);
06213 
06214 
06215     /* Connect the splitter to the sound device */
06216     status = pjmedia_snd_port_connect(app_config.snd, app_config.sc);
06217     pj_assert(status == PJ_SUCCESS);
06218 
06219 }
06220 #endif
06221 
06222 static pj_status_t create_ipv6_media_transports(void)
06223 {
06224     pjsua_media_transport tp[PJSUA_MAX_CALLS];
06225     pj_status_t status;
06226     int port = app_config.rtp_cfg.port;
06227     unsigned i;
06228 
06229     for (i=0; i<app_config.cfg.max_calls; ++i) {
06230         enum { MAX_RETRY = 10 };
06231         pj_sock_t sock[2];
06232         pjmedia_sock_info si;
06233         unsigned j;
06234 
06235         /* Get rid of uninitialized var compiler warning with MSVC */
06236         status = PJ_SUCCESS;
06237 
06238         for (j=0; j<MAX_RETRY; ++j) {
06239             unsigned k;
06240 
06241             for (k=0; k<2; ++k) {
06242                 pj_sockaddr bound_addr;
06243 
06244                 status = pj_sock_socket(pj_AF_INET6(), pj_SOCK_DGRAM(), 0, &sock[k]);
06245                 if (status != PJ_SUCCESS)
06246                     break;
06247 
06248                 status = pj_sockaddr_init(pj_AF_INET6(), &bound_addr,
06249                                           &app_config.rtp_cfg.bound_addr, 
06250                                           (unsigned short)(port+k));
06251                 if (status != PJ_SUCCESS)
06252                     break;
06253 
06254                 status = pj_sock_bind(sock[k], &bound_addr, 
06255                                       pj_sockaddr_get_len(&bound_addr));
06256                 if (status != PJ_SUCCESS)
06257                     break;
06258             }
06259             if (status != PJ_SUCCESS) {
06260                 if (k==1)
06261                     pj_sock_close(sock[0]);
06262 
06263                 if (port != 0)
06264                     port += 10;
06265                 else
06266                     break;
06267 
06268                 continue;
06269             }
06270 
06271             pj_bzero(&si, sizeof(si));
06272             si.rtp_sock = sock[0];
06273             si.rtcp_sock = sock[1];
06274         
06275             pj_sockaddr_init(pj_AF_INET6(), &si.rtp_addr_name, 
06276                              &app_config.rtp_cfg.public_addr, 
06277                              (unsigned short)(port));
06278             pj_sockaddr_init(pj_AF_INET6(), &si.rtcp_addr_name, 
06279                              &app_config.rtp_cfg.public_addr, 
06280                              (unsigned short)(port+1));
06281 
06282             status = pjmedia_transport_udp_attach(pjsua_get_pjmedia_endpt(),
06283                                                   NULL,
06284                                                   &si,
06285                                                   0,
06286                                                   &tp[i].transport);
06287             if (port != 0)
06288                 port += 10;
06289             else
06290                 break;
06291 
06292             if (status == PJ_SUCCESS)
06293                 break;
06294         }
06295 
06296         if (status != PJ_SUCCESS) {
06297             pjsua_perror(THIS_FILE, "Error creating IPv6 UDP media transport", 
06298                          status);
06299             for (j=0; j<i; ++j) {
06300                 pjmedia_transport_close(tp[j].transport);
06301             }
06302             return status;
06303         }
06304     }
06305 
06306 #if DISABLED_FOR_TICKET_1185
06307     return pjsua_media_transports_attach(tp, i, PJ_TRUE);
06308 #else
06309     return PJ_ENOTSUP;
06310 #endif
06311 }
06312 

 


PJSIP Open Source, high performance, small footprint, and very very portable SIP stack
Copyright (C) 2006-2008 Teluu Inc.