📄 pjsua_app.c
字号:
/* $Id: pjsua_app.c 1272 2007-05-14 16:45:20Z bennylp $ */
/*
* Copyright (C) 2003-2007 Benny Prijono <benny@prijono.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <pjsua-lib/pjsua.h>
#define THIS_FILE "pjsua.c"
#define NO_LIMIT (int)0x7FFFFFFF
//#define STEREO_DEMO
/* Call specific data */
struct call_data
{
pj_timer_entry timer;
};
/* Pjsua application data */
static struct app_config
{
pjsua_config cfg;
pjsua_logging_config log_cfg;
pjsua_media_config media_cfg;
pj_bool_t no_refersub;
pj_bool_t no_tcp;
pj_bool_t no_udp;
pj_bool_t use_tls;
pjsua_transport_config udp_cfg;
pjsua_transport_config rtp_cfg;
unsigned acc_cnt;
pjsua_acc_config acc_cfg[PJSUA_MAX_ACC];
unsigned buddy_cnt;
pjsua_buddy_config buddy_cfg[PJSUA_MAX_BUDDIES];
struct call_data call_data[PJSUA_MAX_CALLS];
pj_pool_t *pool;
/* Compatibility with older pjsua */
unsigned codec_cnt;
pj_str_t codec_arg[32];
pj_bool_t null_audio;
unsigned wav_count;
pj_str_t wav_files[32];
unsigned tone_count;
pjmedia_tone_desc tones[32];
pjsua_conf_port_id tone_slots[32];
pjsua_player_id wav_id;
pjsua_conf_port_id wav_port;
pj_bool_t auto_play;
pj_bool_t auto_loop;
pj_bool_t auto_conf;
pj_str_t rec_file;
pj_bool_t auto_rec;
pjsua_recorder_id rec_id;
pjsua_conf_port_id rec_port;
unsigned auto_answer;
unsigned duration;
#ifdef STEREO_DEMO
pjmedia_snd_port *snd;
#endif
float mic_level,
speaker_level;
int capture_dev, playback_dev;
} app_config;
//static pjsua_acc_id current_acc;
#define current_acc pjsua_acc_get_default()
static pjsua_call_id current_call = PJSUA_INVALID_ID;
static pj_str_t uri_arg;
#ifdef STEREO_DEMO
static void stereo_demo();
#endif
pj_status_t app_destroy(void);
/*****************************************************************************
* Configuration manipulation
*/
/* Show usage */
static void usage(void)
{
puts ("Usage:");
puts (" pjsua [options] [SIP URL to call]");
puts ("");
puts ("General options:");
puts (" --config-file=file Read the config/arguments from file.");
puts (" --help Display this help screen");
puts (" --version Display version info");
puts ("");
puts ("Logging options:");
puts (" --log-file=fname Log to filename (default stderr)");
puts (" --log-level=N Set log max level to N (0(none) to 6(trace)) (default=5)");
puts (" --app-log-level=N Set log max level for stdout display (default=4)");
puts ("");
puts ("SIP Account options:");
puts (" --registrar=url Set the URL of registrar server");
puts (" --id=url Set the URL of local ID (used in From header)");
puts (" --contact=url Optionally override the Contact information");
puts (" --proxy=url Optional URL of proxy server to visit");
puts (" May be specified multiple times");
puts (" --reg-timeout=SEC Optional registration interval (default 55)");
puts (" --realm=string Set realm");
puts (" --username=string Set authentication username");
puts (" --password=string Set authentication password");
puts (" --publish Send presence PUBLISH for this account");
puts (" --next-cred Add another credentials");
puts ("");
puts ("SIP Account Control:");
puts (" --next-account Add more account");
puts ("");
puts ("Transport Options:");
puts (" --local-port=port Set TCP/UDP port. This implicitly enables both ");
puts (" TCP and UDP transports on the specified port, unless");
puts (" if TCP or UDP is disabled.");
puts (" --ip-addr=IP Use the specifed address as SIP and RTP addresses.");
puts (" (Hint: the IP may be the public IP of the NAT/router)");
puts (" --no-tcp Disable TCP transport.");
puts (" --no-udp Disable UDP transport.");
puts (" --nameserver=NS Add the specified nameserver to enable SRV resolution");
puts (" This option can be specified multiple times.");
puts (" --outbound=url Set the URL of global outbound proxy server");
puts (" May be specified multiple times");
puts (" --stun-srv=name Set STUN server host or domain");
puts ("");
puts ("TLS Options:");
puts (" --use-tls Enable TLS transport (default=no)");
puts (" --tls-ca-file Specify TLS CA file (default=none)");
puts (" --tls-cert-file Specify TLS certificate file (default=none)");
puts (" --tls-privkey-file Specify TLS private key file (default=none)");
puts (" --tls-password Specify TLS password to private key file (default=none)");
puts (" --tls-verify-server Verify server's certificate (default=no)");
puts (" --tls-verify-client Verify client's certificate (default=no)");
puts (" --tls-neg-timeout Specify TLS negotiation timeout (default=no)");
puts ("");
puts ("Media Options:");
puts (" --use-ice Enable ICE (default:no)");
puts (" --add-codec=name Manually add codec (default is to enable all)");
puts (" --clock-rate=N Override sound device clock rate");
puts (" --null-audio Use NULL audio device");
puts (" --play-file=file Register WAV file in conference bridge.");
puts (" This can be specified multiple times.");
puts (" --play-tone=FORMAT Register tone to the conference bridge.");
puts (" FORMAT is 'F1,F2,ON,OFF', where F1,F2 are");
puts (" frequencies, and ON,OFF=on/off duration in msec.");
puts (" This can be specified multiple times.");
puts (" --auto-play Automatically play the file (to incoming calls only)");
puts (" --auto-loop Automatically loop incoming RTP to outgoing RTP");
puts (" --auto-conf Automatically put calls in conference with others");
puts (" --rec-file=file Open file recorder (extension can be .wav or .mp3");
puts (" --auto-rec Automatically record conversation");
puts (" --rtp-port=N Base port to try for RTP (default=4000)");
puts (" --quality=N Specify media quality (0-10, default=6)");
puts (" --ptime=MSEC Override codec ptime to MSEC (default=specific)");
puts (" --no-vad Disable VAD/silence detector (default=vad enabled)");
puts (" --ec-tail=MSEC Set echo canceller tail length (default=256)");
puts (" --ilbc-mode=MODE Set iLBC codec mode (20 or 30, default is 20)");
puts (" --rx-drop-pct=PCT Drop PCT percent of RX RTP (for pkt lost sim, default: 0)");
puts (" --tx-drop-pct=PCT Drop PCT percent of TX RTP (for pkt lost sim, default: 0)");
puts (" --capture-dev=id Audio capture device ID (default=-1)");
puts (" --playback-dev=id Audio playback device ID (default=-1)");
puts ("");
puts ("Buddy List (can be more than one):");
puts (" --add-buddy url Add the specified URL to the buddy list.");
puts ("");
puts ("User Agent options:");
puts (" --auto-answer=code Automatically answer incoming calls with code (e.g. 200)");
puts (" --max-calls=N Maximum number of concurrent calls (default:4, max:255)");
puts (" --thread-cnt=N Number of worker threads (default:1)");
puts (" --duration=SEC Set maximum call duration (default:no limit)");
puts (" --norefersub Suppress event subscription when transfering calls");
puts ("");
puts ("When URL is specified, pjsua will immediately initiate call to that URL");
puts ("");
fflush(stdout);
}
/* Set default config. */
static void default_config(struct app_config *cfg)
{
char tmp[80];
unsigned i;
pjsua_config_default(&cfg->cfg);
pj_ansi_sprintf(tmp, "PJSUA v%s/%s", PJ_VERSION, PJ_OS_NAME);
pj_strdup2_with_null(app_config.pool, &cfg->cfg.user_agent, tmp);
pjsua_logging_config_default(&cfg->log_cfg);
pjsua_media_config_default(&cfg->media_cfg);
pjsua_transport_config_default(&cfg->udp_cfg);
cfg->udp_cfg.port = 5060;
pjsua_transport_config_default(&cfg->rtp_cfg);
cfg->rtp_cfg.port = 4000;
cfg->duration = NO_LIMIT;
cfg->wav_id = PJSUA_INVALID_ID;
cfg->rec_id = PJSUA_INVALID_ID;
cfg->wav_port = PJSUA_INVALID_ID;
cfg->rec_port = PJSUA_INVALID_ID;
cfg->mic_level = cfg->speaker_level = 1.0;
cfg->capture_dev = PJSUA_INVALID_ID;
cfg->playback_dev = PJSUA_INVALID_ID;
for (i=0; i<PJ_ARRAY_SIZE(cfg->acc_cfg); ++i)
pjsua_acc_config_default(&cfg->acc_cfg[i]);
for (i=0; i<PJ_ARRAY_SIZE(cfg->buddy_cfg); ++i)
pjsua_buddy_config_default(&cfg->buddy_cfg[i]);
}
/*
* Read command arguments from config file.
*/
static int read_config_file(pj_pool_t *pool, const char *filename,
int *app_argc, char ***app_argv)
{
int i;
FILE *fhnd;
char line[200];
int argc = 0;
char **argv;
enum { MAX_ARGS = 64 };
/* Allocate MAX_ARGS+1 (argv needs to be terminated with NULL argument) */
argv = pj_pool_calloc(pool, MAX_ARGS+1, sizeof(char*));
argv[argc++] = *app_argv[0];
/* Open config file. */
fhnd = fopen(filename, "rt");
if (!fhnd) {
PJ_LOG(1,(THIS_FILE, "Unable to open config file %s", filename));
fflush(stdout);
return -1;
}
/* Scan tokens in the file. */
while (argc < MAX_ARGS && !feof(fhnd)) {
char *token, *p = line;
if (fgets(line, sizeof(line), fhnd) == NULL) break;
for (token = strtok(p, " \t\r\n"); argc < MAX_ARGS;
token = strtok(NULL, " \t\r\n"))
{
int token_len;
if (!token) break;
if (*token == '#') break;
token_len = strlen(token);
if (!token_len)
continue;
argv[argc] = pj_pool_alloc(pool, token_len+1);
pj_memcpy(argv[argc], token, token_len+1);
++argc;
}
}
/* Copy arguments from command line */
for (i=1; i<*app_argc && argc < MAX_ARGS; ++i)
argv[argc++] = (*app_argv)[i];
if (argc == MAX_ARGS && (i!=*app_argc || !feof(fhnd))) {
PJ_LOG(1,(THIS_FILE,
"Too many arguments specified in cmd line/config file"));
fflush(stdout);
fclose(fhnd);
return -1;
}
fclose(fhnd);
/* Assign the new command line back to the original command line. */
*app_argc = argc;
*app_argv = argv;
return 0;
}
static int my_atoi(const char *cs)
{
pj_str_t s;
return pj_strtoul(pj_cstr(&s, cs));
}
/* Parse arguments. */
static pj_status_t parse_args(int argc, char *argv[],
struct app_config *cfg,
pj_str_t *uri_to_call)
{
int c;
int option_index;
enum { OPT_CONFIG_FILE=127, OPT_LOG_FILE, OPT_LOG_LEVEL, OPT_APP_LOG_LEVEL,
OPT_HELP, OPT_VERSION, OPT_NULL_AUDIO,
OPT_LOCAL_PORT, OPT_IP_ADDR, OPT_PROXY, OPT_OUTBOUND_PROXY,
OPT_REGISTRAR, OPT_REG_TIMEOUT, OPT_PUBLISH, OPT_ID, OPT_CONTACT,
OPT_REALM, OPT_USERNAME, OPT_PASSWORD,
OPT_NAMESERVER, OPT_STUN_DOMAIN, OPT_STUN_SRV,
OPT_ADD_BUDDY, OPT_OFFER_X_MS_MSG, OPT_NO_PRESENCE,
OPT_AUTO_ANSWER, OPT_AUTO_HANGUP, OPT_AUTO_PLAY, OPT_AUTO_LOOP,
OPT_AUTO_CONF, OPT_CLOCK_RATE, OPT_USE_ICE,
OPT_PLAY_FILE, OPT_PLAY_TONE, OPT_RTP_PORT, OPT_ADD_CODEC,
OPT_ILBC_MODE, OPT_REC_FILE, OPT_AUTO_REC,
OPT_COMPLEXITY, OPT_QUALITY, OPT_PTIME, OPT_NO_VAD,
OPT_RX_DROP_PCT, OPT_TX_DROP_PCT, OPT_EC_TAIL,
OPT_NEXT_ACCOUNT, OPT_NEXT_CRED, OPT_MAX_CALLS,
OPT_DURATION, OPT_NO_TCP, OPT_NO_UDP, OPT_THREAD_CNT,
OPT_NOREFERSUB,
OPT_USE_TLS, OPT_TLS_CA_FILE, OPT_TLS_CERT_FILE, OPT_TLS_PRIV_FILE,
OPT_TLS_PASSWORD, OPT_TLS_VERIFY_SERVER, OPT_TLS_VERIFY_CLIENT,
OPT_TLS_NEG_TIMEOUT,
OPT_CAPTURE_DEV, OPT_PLAYBACK_DEV,
};
struct pj_getopt_option long_options[] = {
{ "config-file",1, 0, OPT_CONFIG_FILE},
{ "log-file", 1, 0, OPT_LOG_FILE},
{ "log-level", 1, 0, OPT_LOG_LEVEL},
{ "app-log-level",1,0,OPT_APP_LOG_LEVEL},
{ "help", 0, 0, OPT_HELP},
{ "version", 0, 0, OPT_VERSION},
{ "clock-rate", 1, 0, OPT_CLOCK_RATE},
{ "null-audio", 0, 0, OPT_NULL_AUDIO},
{ "local-port", 1, 0, OPT_LOCAL_PORT},
{ "ip-addr", 1, 0, OPT_IP_ADDR},
{ "no-tcp", 0, 0, OPT_NO_TCP},
{ "no-udp", 0, 0, OPT_NO_UDP},
{ "norefersub", 0, 0, OPT_NOREFERSUB},
{ "proxy", 1, 0, OPT_PROXY},
{ "outbound", 1, 0, OPT_OUTBOUND_PROXY},
{ "registrar", 1, 0, OPT_REGISTRAR},
{ "reg-timeout",1, 0, OPT_REG_TIMEOUT},
{ "publish", 0, 0, OPT_PUBLISH},
{ "id", 1, 0, OPT_ID},
{ "contact", 1, 0, OPT_CONTACT},
{ "realm", 1, 0, OPT_REALM},
{ "username", 1, 0, OPT_USERNAME},
{ "password", 1, 0, OPT_PASSWORD},
{ "nameserver", 1, 0, OPT_NAMESERVER},
{ "stun-domain",1, 0, OPT_STUN_DOMAIN},
{ "stun-srv", 1, 0, OPT_STUN_SRV},
{ "add-buddy", 1, 0, OPT_ADD_BUDDY},
{ "offer-x-ms-msg",0,0,OPT_OFFER_X_MS_MSG},
{ "no-presence", 0, 0, OPT_NO_PRESENCE},
{ "auto-answer",1, 0, OPT_AUTO_ANSWER},
{ "auto-hangup",1, 0, OPT_AUTO_HANGUP},
{ "auto-play", 0, 0, OPT_AUTO_PLAY},
{ "auto-rec", 0, 0, OPT_AUTO_REC},
{ "auto-loop", 0, 0, OPT_AUTO_LOOP},
{ "auto-conf", 0, 0, OPT_AUTO_CONF},
{ "play-file", 1, 0, OPT_PLAY_FILE},
{ "play-tone", 1, 0, OPT_PLAY_TONE},
{ "rec-file", 1, 0, OPT_REC_FILE},
{ "rtp-port", 1, 0, OPT_RTP_PORT},
{ "use-ice", 0, 0, OPT_USE_ICE},
{ "add-codec", 1, 0, OPT_ADD_CODEC},
{ "complexity", 1, 0, OPT_COMPLEXITY},
{ "quality", 1, 0, OPT_QUALITY},
{ "ptime", 1, 0, OPT_PTIME},
{ "no-vad", 0, 0, OPT_NO_VAD},
{ "ec-tail", 1, 0, OPT_EC_TAIL},
{ "ilbc-mode", 1, 0, OPT_ILBC_MODE},
{ "rx-drop-pct",1, 0, OPT_RX_DROP_PCT},
{ "tx-drop-pct",1, 0, OPT_TX_DROP_PCT},
{ "next-account",0,0, OPT_NEXT_ACCOUNT},
{ "next-cred", 0, 0, OPT_NEXT_CRED},
{ "max-calls", 1, 0, OPT_MAX_CALLS},
{ "duration", 1, 0, OPT_DURATION},
{ "thread-cnt", 1, 0, OPT_THREAD_CNT},
{ "use-tls", 0, 0, OPT_USE_TLS},
{ "tls-ca-file",1, 0, OPT_TLS_CA_FILE},
{ "tls-cert-file",1,0, OPT_TLS_CERT_FILE},
{ "tls-privkey-file",1,0, OPT_TLS_PRIV_FILE},
{ "tls-password",1,0, OPT_TLS_PASSWORD},
{ "tls-verify-server", 0, 0, OPT_TLS_VERIFY_SERVER},
{ "tls-verify-client", 0, 0, OPT_TLS_VERIFY_CLIENT},
{ "tls-neg-timeout", 1, 0, OPT_TLS_NEG_TIMEOUT},
{ "capture-dev", 1, 0, OPT_CAPTURE_DEV},
{ "playback-dev", 1, 0, OPT_PLAYBACK_DEV},
{ NULL, 0, 0, 0}
};
pj_status_t status;
pjsua_acc_config *cur_acc;
char *config_file = NULL;
unsigned i;
/* Run pj_getopt once to see if user specifies config file to read. */
pj_optind = 0;
while ((c=pj_getopt_long(argc, argv, "", long_options,
&option_index)) != -1)
{
switch (c) {
case OPT_CONFIG_FILE:
config_file = pj_optarg;
break;
}
if (config_file)
break;
}
if (config_file) {
status = read_config_file(app_config.pool, config_file, &argc, &argv);
if (status != 0)
return status;
}
cfg->acc_cnt = 0;
cur_acc = &cfg->acc_cfg[0];
/* Reinitialize and re-run pj_getopt again, possibly with new arguments
* read from config file.
*/
pj_optind = 0;
while((c=pj_getopt_long(argc,argv, "", long_options,&option_index))!=-1) {
pj_str_t tmp;
long lval;
switch (c) {
case OPT_CONFIG_FILE:
/* Ignore as this has been processed before */
break;
case OPT_LOG_FILE:
cfg->log_cfg.log_filename = pj_str(pj_optarg);
break;
case OPT_LOG_LEVEL:
c = pj_strtoul(pj_cstr(&tmp, pj_optarg));
if (c < 0 || c > 6) {
PJ_LOG(1,(THIS_FILE,
"Error: expecting integer value 0-6 "
"for --log-level"));
return PJ_EINVAL;
}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -