summaryrefslogtreecommitdiff
path: root/pjsip-apps/src/symbian_ua/ua.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'pjsip-apps/src/symbian_ua/ua.cpp')
-rw-r--r--pjsip-apps/src/symbian_ua/ua.cpp1186
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;
+}
+