diff options
Diffstat (limited to 'pjsip-apps/src/symbian_ua/ua.cpp')
-rw-r--r-- | pjsip-apps/src/symbian_ua/ua.cpp | 1186 |
1 files changed, 1186 insertions, 0 deletions
diff --git a/pjsip-apps/src/symbian_ua/ua.cpp b/pjsip-apps/src/symbian_ua/ua.cpp new file mode 100644 index 0000000..53e2329 --- /dev/null +++ b/pjsip-apps/src/symbian_ua/ua.cpp @@ -0,0 +1,1186 @@ +/* $Id: ua.cpp 3999 2012-03-30 07:10:13Z bennylp $ */ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 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> +#include <pjsua-lib/pjsua_internal.h> +//#include <pjmedia/symbian_sound_aps.h> +#include "ua.h" + +#define THIS_FILE "symbian_ua.cpp" +#define CON_LOG_LEVEL 3 // console log level +#define FILE_LOG_LEVEL 4 // logfile log level + +// +// Basic config. +// +#define SIP_PORT 5060 + + +// +// Destination URI (to make call, or to subscribe presence) +// +#define SIP_DST_URI "<sip:100@pjsip.lab>" + +// +// Account +// +#define HAS_SIP_ACCOUNT 0 // 1 to enable registration +#define SIP_DOMAIN "pjsip.lab" +#define SIP_USER "400" +#define SIP_PASSWD "400" + +// +// Outbound proxy for all accounts +// +#define SIP_PROXY NULL +//#define SIP_PROXY "<sip:192.168.0.8;lr>" + +// +// SIP transports +// +#define ENABLE_SIP_UDP 1 +#define ENABLE_SIP_TCP 0 // experimental +#define ENABLE_SIP_TLS 0 // experimental + +// +// Configure nameserver if DNS SRV is to be used with both SIP +// or STUN (for STUN see other settings below) +// +#define NAMESERVER NULL +//#define NAMESERVER "192.168.0.2" + +// +// STUN server +#if 0 + // Use this to have the STUN server resolved normally +# define STUN_DOMAIN NULL +# define STUN_SERVER "stun.pjsip.org" +#elif 0 + // Use this to have the STUN server resolved with DNS SRV +# define STUN_DOMAIN "pjsip.org" +# define STUN_SERVER NULL +#else + // Use this to disable STUN +# define STUN_DOMAIN NULL +# define STUN_SERVER NULL +#endif + +// +// Use ICE? +// +#define USE_ICE 1 + +// +// Use SRTP? +// +#define USE_SRTP PJSUA_DEFAULT_USE_SRTP + +// +// Set QoS on transports? Yes! +// As an example, we set SIP transports DSCP value to CS3 (DSCP +// value 24 or 0x18), for no reason, and tag RTP/RTCP packets +// with VOICE type. +// +#define SIP_QOS_DSCP 0x18 +#define RTP_QOS_TYPE PJ_QOS_TYPE_VOICE + + +// +// Globals +// +static pjsua_acc_id g_acc_id = PJSUA_INVALID_ID; +static pjsua_call_id g_call_id = PJSUA_INVALID_ID; +static pjsua_buddy_id g_buddy_id = PJSUA_INVALID_ID; + + +/* Callback called by the library upon receiving incoming call */ +static void on_incoming_call(pjsua_acc_id acc_id, pjsua_call_id call_id, + pjsip_rx_data *rdata) +{ + pjsua_call_info ci; + + PJ_UNUSED_ARG(acc_id); + PJ_UNUSED_ARG(rdata); + + if (g_call_id != PJSUA_INVALID_ID) { + pjsua_call_answer(call_id, PJSIP_SC_BUSY_HERE, NULL, NULL); + return; + } + + pjsua_call_get_info(call_id, &ci); + + PJ_LOG(3,(THIS_FILE, "Incoming call from %.*s!!", + (int)ci.remote_info.slen, + ci.remote_info.ptr)); + + g_call_id = call_id; + + /* Automatically answer incoming calls with 180/Ringing */ + pjsua_call_answer(call_id, 180, NULL, NULL); +} + +/* Callback called by the library when call's state has changed */ +static void on_call_state(pjsua_call_id call_id, pjsip_event *e) +{ + pjsua_call_info ci; + + PJ_UNUSED_ARG(e); + + pjsua_call_get_info(call_id, &ci); + + if (ci.state == PJSIP_INV_STATE_DISCONNECTED) { + if (call_id == g_call_id) + g_call_id = PJSUA_INVALID_ID; + } else if (ci.state != PJSIP_INV_STATE_INCOMING) { + if (g_call_id == PJSUA_INVALID_ID) + g_call_id = call_id; + } + + PJ_LOG(3,(THIS_FILE, "Call %d state=%.*s", call_id, + (int)ci.state_text.slen, + ci.state_text.ptr)); +} + +/* Callback called by the library when call's media state has changed */ +static void on_call_media_state(pjsua_call_id call_id) +{ + pjsua_call_info ci; + + pjsua_call_get_info(call_id, &ci); + + if (ci.media_status == PJSUA_CALL_MEDIA_ACTIVE) { + // When media is active, connect call to sound device. + pjsua_conf_connect(ci.conf_slot, 0); + pjsua_conf_connect(0, ci.conf_slot); + } +} + + +/* Handler on buddy state changed. */ +static void on_buddy_state(pjsua_buddy_id buddy_id) +{ + pjsua_buddy_info info; + pjsua_buddy_get_info(buddy_id, &info); + + PJ_LOG(3,(THIS_FILE, "%.*s status is %.*s", + (int)info.uri.slen, + info.uri.ptr, + (int)info.status_text.slen, + info.status_text.ptr)); +} + + +/* Incoming IM message (i.e. MESSAGE request)! */ +static void on_pager(pjsua_call_id call_id, const pj_str_t *from, + const pj_str_t *to, const pj_str_t *contact, + const pj_str_t *mime_type, const pj_str_t *text) +{ + /* Note: call index may be -1 */ + PJ_UNUSED_ARG(call_id); + PJ_UNUSED_ARG(to); + PJ_UNUSED_ARG(contact); + PJ_UNUSED_ARG(mime_type); + + PJ_LOG(3,(THIS_FILE,"MESSAGE from %.*s: %.*s", + (int)from->slen, from->ptr, + (int)text->slen, text->ptr)); +} + + +/* Received typing indication */ +static void on_typing(pjsua_call_id call_id, const pj_str_t *from, + const pj_str_t *to, const pj_str_t *contact, + pj_bool_t is_typing) +{ + PJ_UNUSED_ARG(call_id); + PJ_UNUSED_ARG(to); + PJ_UNUSED_ARG(contact); + + PJ_LOG(3,(THIS_FILE, "IM indication: %.*s %s", + (int)from->slen, from->ptr, + (is_typing?"is typing..":"has stopped typing"))); +} + + +/* Call transfer request status. */ +static void on_call_transfer_status(pjsua_call_id call_id, + int status_code, + const pj_str_t *status_text, + pj_bool_t final, + pj_bool_t *p_cont) +{ + PJ_LOG(3,(THIS_FILE, "Call %d: transfer status=%d (%.*s) %s", + call_id, status_code, + (int)status_text->slen, status_text->ptr, + (final ? "[final]" : ""))); + + if (status_code/100 == 2) { + PJ_LOG(3,(THIS_FILE, + "Call %d: call transfered successfully, disconnecting call", + call_id)); + pjsua_call_hangup(call_id, PJSIP_SC_GONE, NULL, NULL); + *p_cont = PJ_FALSE; + } +} + + +/* NAT detection result */ +static void on_nat_detect(const pj_stun_nat_detect_result *res) +{ + if (res->status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "NAT detection failed", res->status); + } else { + PJ_LOG(3, (THIS_FILE, "NAT detected as %s", res->nat_type_name)); + } +} + +/* Notification that call is being replaced. */ +static void on_call_replaced(pjsua_call_id old_call_id, + pjsua_call_id new_call_id) +{ + pjsua_call_info old_ci, new_ci; + + pjsua_call_get_info(old_call_id, &old_ci); + pjsua_call_get_info(new_call_id, &new_ci); + + PJ_LOG(3,(THIS_FILE, "Call %d with %.*s is being replaced by " + "call %d with %.*s", + old_call_id, + (int)old_ci.remote_info.slen, old_ci.remote_info.ptr, + new_call_id, + (int)new_ci.remote_info.slen, new_ci.remote_info.ptr)); +} + +/* + * Transport status notification + */ +static void on_transport_state(pjsip_transport *tp, + pjsip_transport_state state, + const pjsip_transport_state_info *info) +{ + char host_port[128]; + + pj_ansi_snprintf(host_port, sizeof(host_port), "[%.*s:%d]", + (int)tp->remote_name.host.slen, + tp->remote_name.host.ptr, + tp->remote_name.port); + + switch (state) { + case PJSIP_TP_STATE_CONNECTED: + { + PJ_LOG(3,(THIS_FILE, "SIP transport %s is connected to %s", + tp->type_name, host_port)); + } + break; + + case PJSIP_TP_STATE_DISCONNECTED: + { + char buf[100]; + + snprintf(buf, sizeof(buf), "SIP transport %s is disconnected from %s", + tp->type_name, host_port); + pjsua_perror(THIS_FILE, buf, info->status); + } + break; + + default: + break; + } + +#if defined(PJSIP_HAS_TLS_TRANSPORT) && PJSIP_HAS_TLS_TRANSPORT!=0 + + if (!pj_ansi_stricmp(tp->type_name, "tls") && info->ext_info && + state == PJSIP_TP_STATE_CONNECTED) + { + pjsip_tls_state_info *tls_info = (pjsip_tls_state_info*)info->ext_info; + pj_ssl_sock_info *ssl_sock_info = (pj_ssl_sock_info*) + tls_info->ssl_sock_info; + char buf[2048]; + + /* Dump server TLS certificate */ + pj_ssl_cert_info_dump(ssl_sock_info->remote_cert_info, " ", + buf, sizeof(buf)); + PJ_LOG(4,(THIS_FILE, "TLS cert info of %s:\n%s", host_port, buf)); + } + +#endif +} + + +//#include<e32debug.h> + +/* Logging callback */ +static void log_writer(int level, const char *buf, int len) +{ + static wchar_t buf16[PJ_LOG_MAX_SIZE]; + + PJ_UNUSED_ARG(level); + + pj_ansi_to_unicode(buf, len, buf16, PJ_ARRAY_SIZE(buf16)); + + TPtrC16 aBuf((const TUint16*)buf16, (TInt)len); + //RDebug::Print(aBuf); + console->Write(aBuf); + +} + +/* + * app_startup() + * + * url may contain URL to call. + */ +static pj_status_t app_startup() +{ + pj_status_t status; + + /* Redirect log before pjsua_init() */ + pj_log_set_log_func(&log_writer); + + /* Set log level */ + pj_log_set_level(CON_LOG_LEVEL); + + /* Create pjsua first! */ + status = pjsua_create(); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "pjsua_create() error", status); + return status; + } + + /* Init pjsua */ + pjsua_config cfg; + pjsua_logging_config log_cfg; + pjsua_media_config med_cfg; + + pjsua_config_default(&cfg); + cfg.max_calls = 2; + cfg.thread_cnt = 0; // Disable threading on Symbian + cfg.use_srtp = USE_SRTP; + cfg.srtp_secure_signaling = 0; + + cfg.cb.on_incoming_call = &on_incoming_call; + cfg.cb.on_call_media_state = &on_call_media_state; + cfg.cb.on_call_state = &on_call_state; + cfg.cb.on_buddy_state = &on_buddy_state; + cfg.cb.on_pager = &on_pager; + cfg.cb.on_typing = &on_typing; + cfg.cb.on_call_transfer_status = &on_call_transfer_status; + cfg.cb.on_call_replaced = &on_call_replaced; + cfg.cb.on_nat_detect = &on_nat_detect; + cfg.cb.on_transport_state = &on_transport_state; + + if (SIP_PROXY) { + cfg.outbound_proxy_cnt = 1; + cfg.outbound_proxy[0] = pj_str(SIP_PROXY); + } + + if (NAMESERVER) { + cfg.nameserver_count = 1; + cfg.nameserver[0] = pj_str(NAMESERVER); + } + + if (NAMESERVER && STUN_DOMAIN) { + cfg.stun_domain = pj_str(STUN_DOMAIN); + } else if (STUN_SERVER) { + cfg.stun_host = pj_str(STUN_SERVER); + } + + + pjsua_logging_config_default(&log_cfg); + log_cfg.level = FILE_LOG_LEVEL; + log_cfg.console_level = CON_LOG_LEVEL; + log_cfg.cb = &log_writer; + log_cfg.log_filename = pj_str("C:\\data\\symbian_ua.log"); + + pjsua_media_config_default(&med_cfg); + med_cfg.thread_cnt = 0; // Disable threading on Symbian + med_cfg.has_ioqueue = PJ_FALSE; + med_cfg.clock_rate = 8000; + med_cfg.audio_frame_ptime = 40; + med_cfg.ec_tail_len = 0; + med_cfg.enable_ice = USE_ICE; + med_cfg.snd_auto_close_time = 0; // wait for 0 seconds idle before sound dev get auto-closed + //med_cfg.no_vad = PJ_TRUE; + + status = pjsua_init(&cfg, &log_cfg, &med_cfg); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "pjsua_init() error", status); + pjsua_destroy(); + return status; + } + + /* Adjust Speex priority and enable only the narrowband */ + { + pj_str_t codec_id = pj_str("speex/8000"); + pjmedia_codec_mgr_set_codec_priority( + pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt), + &codec_id, PJMEDIA_CODEC_PRIO_NORMAL+1); + + codec_id = pj_str("speex/16000"); + pjmedia_codec_mgr_set_codec_priority( + pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt), + &codec_id, PJMEDIA_CODEC_PRIO_DISABLED); + + codec_id = pj_str("speex/32000"); + pjmedia_codec_mgr_set_codec_priority( + pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt), + &codec_id, PJMEDIA_CODEC_PRIO_DISABLED); + } + + + pjsua_transport_config tcfg; + pjsua_transport_id tid; + +#if ENABLE_SIP_UDP + /* Add UDP transport. */ + pjsua_transport_config_default(&tcfg); + tcfg.port = SIP_PORT; + if (SIP_QOS_DSCP) { + tcfg.qos_params.flags |= PJ_QOS_PARAM_HAS_DSCP; + tcfg.qos_params.dscp_val = SIP_QOS_DSCP; + } + status = pjsua_transport_create(PJSIP_TRANSPORT_UDP, &tcfg, &tid); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error creating UDP transport", status); + pjsua_destroy(); + return status; + } +#endif + +#if ENABLE_SIP_TCP + /* Add TCP transport */ + pjsua_transport_config_default(&tcfg); + tcfg.port = SIP_PORT; + if (SIP_QOS_DSCP) { + tcfg.qos_params.flags |= PJ_QOS_PARAM_HAS_DSCP; + tcfg.qos_params.dscp_val = SIP_QOS_DSCP; + } + status = pjsua_transport_create(PJSIP_TRANSPORT_TCP, &tcfg, &tid); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error creating TCP transport", status); + pjsua_destroy(); + return status; + } +#endif + +#if ENABLE_SIP_TLS + /* Add TLS transport */ + pjsua_transport_config_default(&tcfg); + tcfg.port = SIP_PORT + 1; + if (SIP_QOS_DSCP) { + tcfg.qos_params.flags |= PJ_QOS_PARAM_HAS_DSCP; + tcfg.qos_params.dscp_val = SIP_QOS_DSCP; + tcfg.tls_setting.qos_params = tcfg.qos_params; + } + status = pjsua_transport_create(PJSIP_TRANSPORT_TLS, &tcfg, &tid); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error creating TLS transport", status); + pjsua_destroy(); + return status; + } +#endif + + /* Add account for the transport */ + pjsua_acc_add_local(tid, PJ_TRUE, &g_acc_id); + +#if DISABLED_FOR_TICKET_1185 + /* Create media transports */ + pjsua_transport_config mtcfg; + pjsua_transport_config_default(&mtcfg); + mtcfg.port = 4000; + mtcfg.qos_type = RTP_QOS_TYPE; + status = pjsua_media_transports_create(&mtcfg); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error creating media transports", status); + pjsua_destroy(); + return status; + } +#endif + + /* Initialization is done, now start pjsua */ + status = pjsua_start(); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error starting pjsua", status); + pjsua_destroy(); + return status; + } + + /* Register to SIP server by creating SIP account. */ + if (HAS_SIP_ACCOUNT) { + pjsua_acc_config cfg; + + pjsua_acc_config_default(&cfg); + cfg.id = pj_str("sip:" SIP_USER "@" SIP_DOMAIN); + cfg.reg_uri = pj_str("sip:" SIP_DOMAIN); + cfg.cred_count = 1; + cfg.cred_info[0].realm = pj_str("*"); + cfg.cred_info[0].scheme = pj_str("digest"); + cfg.cred_info[0].username = pj_str(SIP_USER); + cfg.cred_info[0].data_type = PJSIP_CRED_DATA_PLAIN_PASSWD; + cfg.cred_info[0].data = pj_str(SIP_PASSWD); + + status = pjsua_acc_add(&cfg, PJ_TRUE, &g_acc_id); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error adding account", status); + pjsua_destroy(); + return status; + } + } + + if (SIP_DST_URI) { + pjsua_buddy_config bcfg; + + pjsua_buddy_config_default(&bcfg); + bcfg.uri = pj_str(SIP_DST_URI); + bcfg.subscribe = PJ_FALSE; + + pjsua_buddy_add(&bcfg, &g_buddy_id); + } + return PJ_SUCCESS; +} + + +//////////////////////////////////////////////////////////////////////////// +/* + * The interractive console UI + */ +#include <e32base.h> + +class ConsoleUI : public CActive +{ +public: + ConsoleUI(CConsoleBase *con); + ~ConsoleUI(); + + // Run console UI + void Run(); + + // Stop + void Stop(); + +protected: + // Cancel asynchronous read. + void DoCancel(); + + // Implementation: called when read has completed. + void RunL(); + +private: + CConsoleBase *con_; +}; + + +ConsoleUI::ConsoleUI(CConsoleBase *con) +: CActive(EPriorityStandard), con_(con) +{ + CActiveScheduler::Add(this); +} + +ConsoleUI::~ConsoleUI() +{ + Stop(); +} + +// Run console UI +void ConsoleUI::Run() +{ + con_->Read(iStatus); + SetActive(); +} + +// Stop console UI +void ConsoleUI::Stop() +{ + Cancel(); +} + +// Cancel asynchronous read. +void ConsoleUI::DoCancel() +{ + con_->ReadCancel(); +} + +static void PrintMainMenu() +{ + const char *menu = + "\n\n" + "Main Menu:\n" + " d Enable/disable codecs\n" + " m Call " SIP_DST_URI "\n" + " a Answer call\n" + " g Hangup all calls\n" + " t Toggle audio route\n" +#if !defined(PJMEDIA_CONF_USE_SWITCH_BOARD) || PJMEDIA_CONF_USE_SWITCH_BOARD==0 + " j Toggle loopback audio\n" +#endif + "up/dn Increase/decrease output volume\n" + " s Subscribe " SIP_DST_URI "\n" + " S Unsubscribe presence\n" + " o Set account online\n" + " O Set account offline\n" + " w Quit\n"; + + PJ_LOG(3, (THIS_FILE, menu)); +} + +static void PrintCodecMenu() +{ + const char *menu = + "\n\n" + "Codec Menu:\n" + " a Enable all codecs\n" +#if PJMEDIA_HAS_PASSTHROUGH_CODECS && PJMEDIA_HAS_PASSTHROUGH_CODEC_AMR + " d Enable only AMR\n" +#endif +#if PJMEDIA_HAS_PASSTHROUGH_CODECS && PJMEDIA_HAS_PASSTHROUGH_CODEC_G729 + " g Enable only G.729\n" +#endif +#if PJMEDIA_HAS_PASSTHROUGH_CODECS && PJMEDIA_HAS_PASSTHROUGH_CODEC_ILBC + " j Enable only iLBC\n" +#endif + " m Enable only Speex\n" + " p Enable only GSM\n" + " t Enable only PCMU\n" + " w Enable only PCMA\n"; + + PJ_LOG(3, (THIS_FILE, menu)); +} + +static void HandleMainMenu(TKeyCode kc) { + switch (kc) { + + case EKeyUpArrow: + case EKeyDownArrow: + { + unsigned vol; + pj_status_t status; + + status = pjsua_snd_get_setting( + PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING, &vol); + if (status == PJ_SUCCESS) { + if (kc == EKeyUpArrow) + vol = PJ_MIN(100, vol+10); + else + vol = (vol>=10 ? vol-10 : 0); + status = pjsua_snd_set_setting( + PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING, + &vol, PJ_TRUE); + } + + if (status == PJ_SUCCESS) { + PJ_LOG(3,(THIS_FILE, "Output volume set to %d", vol)); + } else { + pjsua_perror(THIS_FILE, "Error setting volume", status); + } + } + break; + + case 't': + { + pjmedia_aud_dev_route route; + pj_status_t status; + + status = pjsua_snd_get_setting(PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE, + &route); + + if (status == PJ_SUCCESS) { + if (route == PJMEDIA_AUD_DEV_ROUTE_LOUDSPEAKER) + route = PJMEDIA_AUD_DEV_ROUTE_EARPIECE; + else + route = PJMEDIA_AUD_DEV_ROUTE_LOUDSPEAKER; + + status = pjsua_snd_set_setting( + PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE, + &route, PJ_TRUE); + } + + if (status != PJ_SUCCESS) + pjsua_perror(THIS_FILE, "Error switch audio route", status); + } + break; + + case 'j': + { + static pj_bool_t loopback_active = PJ_FALSE; + if (!loopback_active) + pjsua_conf_connect(0, 0); + else + pjsua_conf_disconnect(0, 0); + loopback_active = !loopback_active; + } + break; + + case 'm': + if (g_call_id != PJSUA_INVALID_ID) { + PJ_LOG(3,(THIS_FILE, "Another call is active")); + break; + } + + if (pjsua_verify_url(SIP_DST_URI) == PJ_SUCCESS) { + pj_str_t dst = pj_str(SIP_DST_URI); + pjsua_call_make_call(g_acc_id, &dst, 0, NULL, + NULL, &g_call_id); + } else { + PJ_LOG(3,(THIS_FILE, "Invalid SIP URI")); + } + break; + case 'a': + if (g_call_id != PJSUA_INVALID_ID) + pjsua_call_answer(g_call_id, 200, NULL, NULL); + break; + case 'g': + pjsua_call_hangup_all(); + break; + case 's': + case 'S': + if (g_buddy_id != PJSUA_INVALID_ID) + pjsua_buddy_subscribe_pres(g_buddy_id, kc=='s'); + break; + case 'o': + case 'O': + pjsua_acc_set_online_status(g_acc_id, kc=='o'); + break; + + default: + PJ_LOG(3,(THIS_FILE, "Keycode '%c' (%d) is pressed", kc, kc)); + break; + } + + PrintMainMenu(); +} + +static void HandleCodecMenu(TKeyCode kc) { + const pj_str_t ID_ALL = {"*", 1}; + pj_str_t codec = {NULL, 0}; + + if (kc == 'a') { + pjsua_codec_set_priority(&ID_ALL, PJMEDIA_CODEC_PRIO_NORMAL); + PJ_LOG(3,(THIS_FILE, "All codecs activated")); + } else { + switch (kc) { + case 'd': + codec = pj_str("AMR"); + break; + case 'g': + codec = pj_str("G729"); + break; + case 'j': + codec = pj_str("ILBC"); + break; + case 'm': + codec = pj_str("SPEEX/8000"); + break; + case 'p': + codec = pj_str("GSM"); + break; + case 't': + codec = pj_str("PCMU"); + break; + case 'w': + codec = pj_str("PCMA"); + break; + default: + PJ_LOG(3,(THIS_FILE, "Keycode '%c' (%d) is pressed", kc, kc)); + break; + } + + if (codec.slen) { + pj_status_t status; + + pjsua_codec_set_priority(&ID_ALL, PJMEDIA_CODEC_PRIO_DISABLED); + + status = pjsua_codec_set_priority(&codec, + PJMEDIA_CODEC_PRIO_NORMAL); + if (status == PJ_SUCCESS) + PJ_LOG(3,(THIS_FILE, "%s activated", codec.ptr)); + else + PJ_LOG(3,(THIS_FILE, "Failed activating %s, err=%d", + codec.ptr, status)); + } + } +} + +// Implementation: called when read has completed. +void ConsoleUI::RunL() +{ + enum { + MENU_TYPE_MAIN = 0, + MENU_TYPE_CODEC = 1 + }; + static int menu_type = MENU_TYPE_MAIN; + TKeyCode kc = con_->KeyCode(); + pj_bool_t reschedule = PJ_TRUE; + + if (menu_type == MENU_TYPE_MAIN) { + if (kc == 'w') { + CActiveScheduler::Stop(); + reschedule = PJ_FALSE; + } else if (kc == 'd') { + menu_type = MENU_TYPE_CODEC; + PrintCodecMenu(); + } else { + HandleMainMenu(kc); + } + } else { + HandleCodecMenu(kc); + + menu_type = MENU_TYPE_MAIN; + PrintMainMenu(); + } + + if (reschedule) + Run(); +} + +#if 0 +// IP networking related testing +static pj_status_t test_addr(void) +{ + int af; + unsigned i, count; + pj_addrinfo ai[8]; + pj_sockaddr ifs[8]; + const pj_str_t *hostname; + pj_hostent he; + pj_status_t status; + + pj_log_set_log_func(&log_writer); + + status = pj_init(); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "pj_init() error", status); + return status; + } + + af = pj_AF_INET(); + +#if 0 + pj_in_addr in_addr; + pj_str_t aa = pj_str("1.1.1.1"); + in_addr = pj_inet_addr(&aa); + char *the_addr = pj_inet_ntoa(in_addr); + PJ_LOG(3,(THIS_FILE, "IP addr=%s", the_addr)); + + aa = pj_str("192.168.0.15"); + in_addr = pj_inet_addr(&aa); + the_addr = pj_inet_ntoa(in_addr); + PJ_LOG(3,(THIS_FILE, "IP addr=%s", the_addr)); + + aa = pj_str("2.2.2.2"); + in_addr = pj_inet_addr(&aa); + the_addr = pj_inet_ntoa(in_addr); + PJ_LOG(3,(THIS_FILE, "IP addr=%s", the_addr)); + + return -1; +#endif + + // Hostname + hostname = pj_gethostname(); + if (hostname == NULL) { + status = PJ_ERESOLVE; + pjsua_perror(THIS_FILE, "pj_gethostname() error", status); + goto on_return; + } + + PJ_LOG(3,(THIS_FILE, "Hostname: %.*s", hostname->slen, hostname->ptr)); + + // Gethostbyname + status = pj_gethostbyname(hostname, &he); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "pj_gethostbyname() error", status); + } else { + PJ_LOG(3,(THIS_FILE, "gethostbyname: %s", + pj_inet_ntoa(*(pj_in_addr*)he.h_addr))); + } + + // Getaddrinfo + count = PJ_ARRAY_SIZE(ai); + status = pj_getaddrinfo(af, hostname, &count, ai); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "pj_getaddrinfo() error", status); + } else { + for (i=0; i<count; ++i) { + char ipaddr[PJ_INET6_ADDRSTRLEN+2]; + PJ_LOG(3,(THIS_FILE, "Addrinfo: %s", + pj_sockaddr_print(&ai[i].ai_addr, ipaddr, sizeof(ipaddr), 2))); + } + } + + // Enum interface + count = PJ_ARRAY_SIZE(ifs); + status = pj_enum_ip_interface(af, &count, ifs); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "pj_enum_ip_interface() error", status); + } else { + for (i=0; i<count; ++i) { + char ipaddr[PJ_INET6_ADDRSTRLEN+2]; + PJ_LOG(3,(THIS_FILE, "Interface: %s", + pj_sockaddr_print(&ifs[i], ipaddr, sizeof(ipaddr), 2))); + } + } + + // Get default iinterface + status = pj_getdefaultipinterface(af, &ifs[0]); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "pj_getdefaultipinterface() error", status); + } else { + char ipaddr[PJ_INET6_ADDRSTRLEN+2]; + PJ_LOG(3,(THIS_FILE, "Default IP: %s", + pj_sockaddr_print(&ifs[0], ipaddr, sizeof(ipaddr), 2))); + } + + // Get default IP address + status = pj_gethostip(af, &ifs[0]); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "pj_gethostip() error", status); + } else { + char ipaddr[PJ_INET6_ADDRSTRLEN+2]; + PJ_LOG(3,(THIS_FILE, "Host IP: %s", + pj_sockaddr_print(&ifs[0], ipaddr, sizeof(ipaddr), 2))); + } + + status = -1; + +on_return: + pj_shutdown(); + return status; +} +#endif + + +#include <es_sock.h> + +#if 0 +// Force network connection to use the first IAP, +// this is useful for debugging on emulator without GUI. +// Include commdb.lib & apengine.lib in symbian_ua.mmp file +// if this is enabled. + +#include <apdatahandler.h> + +inline void ForceUseFirstIAP() +{ + TUint32 rank = 1; + TUint32 bearers; + TUint32 prompt; + TUint32 iap; + + CCommsDatabase* commDb = CCommsDatabase::NewL(EDatabaseTypeIAP); + CleanupStack::PushL(commDb); + + CApDataHandler* apDataHandler = CApDataHandler::NewLC(*commDb); + + TCommDbConnectionDirection direction = ECommDbConnectionDirectionOutgoing; + apDataHandler->GetPreferredIfDbIapTypeL(rank, direction, bearers, prompt, iap); + prompt = ECommDbDialogPrefDoNotPrompt; + apDataHandler->SetPreferredIfDbIapTypeL(rank, direction, bearers, (TCommDbDialogPref)prompt, iap, ETrue); + CleanupStack::PopAndDestroy(2); // apDataHandler, commDb +} + +static void SelectIAP() +{ + ForceUseFirstIAP(); +} + +#else + +static void SelectIAP() +{ +} + +#endif + + +// Class CConnMon to monitor network connection (RConnection). Whenever +// the connection is down, it will notify PJLIB and restart PJSUA-LIB. +class CConnMon : public CActive { +public: + static CConnMon* NewL(RConnection &conn, RSocketServ &sserver) { + CConnMon *self = new (ELeave) CConnMon(conn, sserver); + CleanupStack::PushL(self); + self->ConstructL(); + CleanupStack::Pop(self); + return self; + } + + void Start() { + conn_.ProgressNotification(nif_progress_, iStatus); + SetActive(); + } + + void Stop() { + Cancel(); + } + + ~CConnMon() { Stop(); } + +private: + CConnMon(RConnection &conn, RSocketServ &sserver) : + CActive(EPriorityHigh), + conn_(conn), + sserver_(sserver) + { + CActiveScheduler::Add(this); + } + + void ConstructL() {} + + void DoCancel() { + conn_.CancelProgressNotification(); + } + + void RunL() { + int stage = nif_progress_().iStage; + + if (stage == KLinkLayerClosed) { + pj_status_t status; + TInt err; + + // Tell pjlib that connection is down. + pj_symbianos_set_connection_status(PJ_FALSE); + + PJ_LOG(3, (THIS_FILE, "RConnection closed, restarting PJSUA..")); + + // Destroy pjsua + pjsua_destroy(); + PJ_LOG(3, (THIS_FILE, "PJSUA destroyed.")); + + // Reopen the connection + err = conn_.Open(sserver_); + if (err == KErrNone) + err = conn_.Start(); + if (err != KErrNone) { + CActiveScheduler::Stop(); + return; + } + + // Reinit Symbian OS param before pj_init() + pj_symbianos_params sym_params; + pj_bzero(&sym_params, sizeof(sym_params)); + sym_params.rsocketserv = &sserver_; + sym_params.rconnection = &conn_; + pj_symbianos_set_params(&sym_params); + + // Reinit pjsua + status = app_startup(); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "app_startup() error", status); + CActiveScheduler::Stop(); + return; + } + + PJ_LOG(3, (THIS_FILE, "PJSUA restarted.")); + PrintMainMenu(); + } + + Start(); + } + + RConnection& conn_; + RSocketServ& sserver_; + TNifProgressBuf nif_progress_; +}; + +//////////////////////////////////////////////////////////////////////////// +int ua_main() +{ + RSocketServ aSocketServer; + RConnection aConn; + TInt err; + pj_symbianos_params sym_params; + pj_status_t status; + + SelectIAP(); + + // Initialize RSocketServ + if ((err=aSocketServer.Connect(32)) != KErrNone) + return PJ_STATUS_FROM_OS(err); + + // Open up a connection + if ((err=aConn.Open(aSocketServer)) != KErrNone) { + aSocketServer.Close(); + return PJ_STATUS_FROM_OS(err); + } + + if ((err=aConn.Start()) != KErrNone) { + aSocketServer.Close(); + return PJ_STATUS_FROM_OS(err); + } + + // Set Symbian OS parameters in pjlib. + // This must be done before pj_init() is called. + pj_bzero(&sym_params, sizeof(sym_params)); + sym_params.rsocketserv = &aSocketServer; + sym_params.rconnection = &aConn; + pj_symbianos_set_params(&sym_params); + + // Initialize pjsua + status = app_startup(); + //status = test_addr(); + if (status != PJ_SUCCESS) { + aConn.Close(); + aSocketServer.Close(); + return status; + } + + + // Run the UI + ConsoleUI *con = new ConsoleUI(console); + + con->Run(); + PrintMainMenu(); + + // Init & start connection monitor + CConnMon *connmon = CConnMon::NewL(aConn, aSocketServer); + connmon->Start(); + + CActiveScheduler::Start(); + + delete connmon; + delete con; + + // Dump memory statistics + PJ_LOG(3,(THIS_FILE, "Max heap usage: %u.%03uMB", + pjsua_var.cp.peak_used_size / 1000000, + (pjsua_var.cp.peak_used_size % 1000000)/1000)); + + // check max stack usage +#if defined(PJ_OS_HAS_CHECK_STACK) && PJ_OS_HAS_CHECK_STACK!=0 + pj_thread_t* this_thread = pj_thread_this(); + if (!this_thread) + return status; + + const char* max_stack_file; + int max_stack_line; + status = pj_thread_get_stack_info(this_thread, &max_stack_file, &max_stack_line); + + PJ_LOG(3,(THIS_FILE, "Max stack usage: %u at %s:%d", + pj_thread_get_stack_max_usage(this_thread), + max_stack_file, max_stack_line)); +#endif + + // Shutdown pjsua + pjsua_destroy(); + + // Close connection and socket server + aConn.Close(); + aSocketServer.Close(); + + return status; +} + |