* #39101: updated pjsip stack, many android build enhancements.

The main issue here is due to the build system of the stack compared to a pure Android
NDK project. Generating arm and x86 ABI at the same time does not seem to be possible.
diff --git a/jni/pjproject-android/pjsip/src/pjsua2/endpoint.cpp b/jni/pjproject-android/pjsip/src/pjsua2/endpoint.cpp
new file mode 100644
index 0000000..4b11bda
--- /dev/null
+++ b/jni/pjproject-android/pjsip/src/pjsua2/endpoint.cpp
@@ -0,0 +1,1612 @@
+/* $Id: endpoint.cpp 4704 2014-01-16 05:30:46Z ming $ */
+/* 
+ * Copyright (C) 2013 Teluu Inc. (http://www.teluu.com)
+ *
+ * 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 <pjsua2/endpoint.hpp>
+#include <pjsua2/account.hpp>
+#include <pjsua2/call.hpp>
+#include <pjsua2/presence.hpp>
+#include <algorithm>
+#include "util.hpp"
+
+using namespace pj;
+using namespace std;
+
+#include <pjsua2/account.hpp>
+#include <pjsua2/call.hpp>
+
+#define THIS_FILE		"endpoint.cpp"
+#define MAX_STUN_SERVERS	32
+#define TIMER_SIGNATURE		0x600D878A
+#define MAX_CODEC_NUM 		64
+
+struct UserTimer
+{
+    pj_uint32_t		signature;
+    OnTimerParam	prm;
+    pj_timer_entry	entry;
+};
+
+Endpoint *Endpoint::instance_;
+
+///////////////////////////////////////////////////////////////////////////////
+
+UaConfig::UaConfig()
+{
+    pjsua_config ua_cfg;
+
+    pjsua_config_default(&ua_cfg);
+    fromPj(ua_cfg);
+}
+
+void UaConfig::fromPj(const pjsua_config &ua_cfg)
+{
+    unsigned i;
+
+    this->maxCalls = ua_cfg.max_calls;
+    this->threadCnt = ua_cfg.thread_cnt;
+    this->userAgent = pj2Str(ua_cfg.user_agent);
+
+    for (i=0; i<ua_cfg.nameserver_count; ++i) {
+	this->nameserver.push_back(pj2Str(ua_cfg.nameserver[i]));
+    }
+
+    for (i=0; i<ua_cfg.stun_srv_cnt; ++i) {
+	this->stunServer.push_back(pj2Str(ua_cfg.stun_srv[i]));
+    }
+
+    this->stunIgnoreFailure = PJ2BOOL(ua_cfg.stun_ignore_failure);
+    this->natTypeInSdp = ua_cfg.nat_type_in_sdp;
+    this->mwiUnsolicitedEnabled = PJ2BOOL(ua_cfg.enable_unsolicited_mwi);
+}
+
+pjsua_config UaConfig::toPj() const
+{
+    unsigned i;
+    pjsua_config pua_cfg;
+
+    pjsua_config_default(&pua_cfg);
+
+    pua_cfg.max_calls = this->maxCalls;
+    pua_cfg.thread_cnt = this->threadCnt;
+    pua_cfg.user_agent = str2Pj(this->userAgent);
+
+    for (i=0; i<this->nameserver.size() && i<PJ_ARRAY_SIZE(pua_cfg.nameserver);
+	 ++i)
+    {
+	pua_cfg.nameserver[i] = str2Pj(this->nameserver[i]);
+    }
+    pua_cfg.nameserver_count = i;
+
+    for (i=0; i<this->stunServer.size() && i<PJ_ARRAY_SIZE(pua_cfg.stun_srv);
+	 ++i)
+    {
+	pua_cfg.stun_srv[i] = str2Pj(this->stunServer[i]);
+    }
+    pua_cfg.stun_srv_cnt = i;
+
+    pua_cfg.nat_type_in_sdp = this->natTypeInSdp;
+    pua_cfg.enable_unsolicited_mwi = this->mwiUnsolicitedEnabled;
+
+    return pua_cfg;
+}
+
+void UaConfig::readObject(const ContainerNode &node) throw(Error)
+{
+    ContainerNode this_node = node.readContainer("UaConfig");
+
+    NODE_READ_UNSIGNED( this_node, maxCalls);
+    NODE_READ_UNSIGNED( this_node, threadCnt);
+    NODE_READ_BOOL    ( this_node, mainThreadOnly);
+    NODE_READ_STRINGV ( this_node, nameserver);
+    NODE_READ_STRING  ( this_node, userAgent);
+    NODE_READ_STRINGV ( this_node, stunServer);
+    NODE_READ_BOOL    ( this_node, stunIgnoreFailure);
+    NODE_READ_INT     ( this_node, natTypeInSdp);
+    NODE_READ_BOOL    ( this_node, mwiUnsolicitedEnabled);
+}
+
+void UaConfig::writeObject(ContainerNode &node) const throw(Error)
+{
+    ContainerNode this_node = node.writeNewContainer("UaConfig");
+
+    NODE_WRITE_UNSIGNED( this_node, maxCalls);
+    NODE_WRITE_UNSIGNED( this_node, threadCnt);
+    NODE_WRITE_BOOL    ( this_node, mainThreadOnly);
+    NODE_WRITE_STRINGV ( this_node, nameserver);
+    NODE_WRITE_STRING  ( this_node, userAgent);
+    NODE_WRITE_STRINGV ( this_node, stunServer);
+    NODE_WRITE_BOOL    ( this_node, stunIgnoreFailure);
+    NODE_WRITE_INT     ( this_node, natTypeInSdp);
+    NODE_WRITE_BOOL    ( this_node, mwiUnsolicitedEnabled);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+LogConfig::LogConfig()
+{
+    pjsua_logging_config lc;
+
+    pjsua_logging_config_default(&lc);
+    fromPj(lc);
+}
+
+void LogConfig::fromPj(const pjsua_logging_config &lc)
+{
+    this->msgLogging = lc.msg_logging;
+    this->level = lc.level;
+    this->consoleLevel = lc.console_level;
+    this->decor = lc.decor;
+    this->filename = pj2Str(lc.log_filename);
+    this->fileFlags = lc.log_file_flags;
+    this->writer = NULL;
+}
+
+pjsua_logging_config LogConfig::toPj() const
+{
+    pjsua_logging_config lc;
+
+    pjsua_logging_config_default(&lc);
+
+    lc.msg_logging = this->msgLogging;
+    lc.level = this->level;
+    lc.console_level = this->consoleLevel;
+    lc.decor = this->decor;
+    lc.log_file_flags = this->fileFlags;
+    lc.log_filename = str2Pj(this->filename);
+
+    return lc;
+}
+
+void LogConfig::readObject(const ContainerNode &node) throw(Error)
+{
+    ContainerNode this_node = node.readContainer("LogConfig");
+
+    NODE_READ_UNSIGNED( this_node, msgLogging);
+    NODE_READ_UNSIGNED( this_node, level);
+    NODE_READ_UNSIGNED( this_node, consoleLevel);
+    NODE_READ_UNSIGNED( this_node, decor);
+    NODE_READ_STRING  ( this_node, filename);
+    NODE_READ_UNSIGNED( this_node, fileFlags);
+}
+
+void LogConfig::writeObject(ContainerNode &node) const throw(Error)
+{
+    ContainerNode this_node = node.writeNewContainer("LogConfig");
+
+    NODE_WRITE_UNSIGNED( this_node, msgLogging);
+    NODE_WRITE_UNSIGNED( this_node, level);
+    NODE_WRITE_UNSIGNED( this_node, consoleLevel);
+    NODE_WRITE_UNSIGNED( this_node, decor);
+    NODE_WRITE_STRING  ( this_node, filename);
+    NODE_WRITE_UNSIGNED( this_node, fileFlags);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+MediaConfig::MediaConfig()
+{
+    pjsua_media_config mc;
+
+    pjsua_media_config_default(&mc);
+    fromPj(mc);
+}
+
+void MediaConfig::fromPj(const pjsua_media_config &mc)
+{
+    this->clockRate = mc.clock_rate;
+    this->sndClockRate = mc.snd_clock_rate;
+    this->channelCount = mc.channel_count;
+    this->audioFramePtime = mc.audio_frame_ptime;
+    this->maxMediaPorts = mc.max_media_ports;
+    this->hasIoqueue = PJ2BOOL(mc.has_ioqueue);
+    this->threadCnt = mc.thread_cnt;
+    this->quality = mc.quality;
+    this->ptime = mc.ptime;
+    this->noVad = PJ2BOOL(mc.no_vad);
+    this->ilbcMode = mc.ilbc_mode;
+    this->txDropPct = mc.tx_drop_pct;
+    this->rxDropPct = mc.rx_drop_pct;
+    this->ecOptions = mc.ec_options;
+    this->ecTailLen = mc.ec_tail_len;
+    this->sndRecLatency = mc.snd_rec_latency;
+    this->sndPlayLatency = mc.snd_play_latency;
+    this->jbInit = mc.jb_init;
+    this->jbMinPre = mc.jb_min_pre;
+    this->jbMaxPre = mc.jb_max_pre;
+    this->jbMax = mc.jb_max;
+    this->sndAutoCloseTime = mc.snd_auto_close_time;
+    this->vidPreviewEnableNative = PJ2BOOL(mc.vid_preview_enable_native);
+}
+
+pjsua_media_config MediaConfig::toPj() const
+{
+    pjsua_media_config mcfg;
+
+    pjsua_media_config_default(&mcfg);
+
+    mcfg.clock_rate = this->clockRate;
+    mcfg.snd_clock_rate = this->sndClockRate;
+    mcfg.channel_count = this->channelCount;
+    mcfg.audio_frame_ptime = this->audioFramePtime;
+    mcfg.max_media_ports = this->maxMediaPorts;
+    mcfg.has_ioqueue = this->hasIoqueue;
+    mcfg.thread_cnt = this->threadCnt;
+    mcfg.quality = this->quality;
+    mcfg.ptime = this->ptime;
+    mcfg.no_vad = this->noVad;
+    mcfg.ilbc_mode = this->ilbcMode;
+    mcfg.tx_drop_pct = this->txDropPct;
+    mcfg.rx_drop_pct = this->rxDropPct;
+    mcfg.ec_options = this->ecOptions;
+    mcfg.ec_tail_len = this->ecTailLen;
+    mcfg.snd_rec_latency = this->sndRecLatency;
+    mcfg.snd_play_latency = this->sndPlayLatency;
+    mcfg.jb_init = this->jbInit;
+    mcfg.jb_min_pre = this->jbMinPre;
+    mcfg.jb_max_pre = this->jbMaxPre;
+    mcfg.jb_max = this->jbMax;
+    mcfg.snd_auto_close_time = this->sndAutoCloseTime;
+    mcfg.vid_preview_enable_native = this->vidPreviewEnableNative;
+
+    return mcfg;
+}
+
+void MediaConfig::readObject(const ContainerNode &node) throw(Error)
+{
+    ContainerNode this_node = node.readContainer("MediaConfig");
+
+    NODE_READ_UNSIGNED( this_node, clockRate);
+    NODE_READ_UNSIGNED( this_node, sndClockRate);
+    NODE_READ_UNSIGNED( this_node, channelCount);
+    NODE_READ_UNSIGNED( this_node, audioFramePtime);
+    NODE_READ_UNSIGNED( this_node, maxMediaPorts);
+    NODE_READ_BOOL    ( this_node, hasIoqueue);
+    NODE_READ_UNSIGNED( this_node, threadCnt);
+    NODE_READ_UNSIGNED( this_node, quality);
+    NODE_READ_UNSIGNED( this_node, ptime);
+    NODE_READ_BOOL    ( this_node, noVad);
+    NODE_READ_UNSIGNED( this_node, ilbcMode);
+    NODE_READ_UNSIGNED( this_node, txDropPct);
+    NODE_READ_UNSIGNED( this_node, rxDropPct);
+    NODE_READ_UNSIGNED( this_node, ecOptions);
+    NODE_READ_UNSIGNED( this_node, ecTailLen);
+    NODE_READ_UNSIGNED( this_node, sndRecLatency);
+    NODE_READ_UNSIGNED( this_node, sndPlayLatency);
+    NODE_READ_INT     ( this_node, jbInit);
+    NODE_READ_INT     ( this_node, jbMinPre);
+    NODE_READ_INT     ( this_node, jbMaxPre);
+    NODE_READ_INT     ( this_node, jbMax);
+    NODE_READ_INT     ( this_node, sndAutoCloseTime);
+    NODE_READ_BOOL    ( this_node, vidPreviewEnableNative);
+}
+
+void MediaConfig::writeObject(ContainerNode &node) const throw(Error)
+{
+    ContainerNode this_node = node.writeNewContainer("MediaConfig");
+
+    NODE_WRITE_UNSIGNED( this_node, clockRate);
+    NODE_WRITE_UNSIGNED( this_node, sndClockRate);
+    NODE_WRITE_UNSIGNED( this_node, channelCount);
+    NODE_WRITE_UNSIGNED( this_node, audioFramePtime);
+    NODE_WRITE_UNSIGNED( this_node, maxMediaPorts);
+    NODE_WRITE_BOOL    ( this_node, hasIoqueue);
+    NODE_WRITE_UNSIGNED( this_node, threadCnt);
+    NODE_WRITE_UNSIGNED( this_node, quality);
+    NODE_WRITE_UNSIGNED( this_node, ptime);
+    NODE_WRITE_BOOL    ( this_node, noVad);
+    NODE_WRITE_UNSIGNED( this_node, ilbcMode);
+    NODE_WRITE_UNSIGNED( this_node, txDropPct);
+    NODE_WRITE_UNSIGNED( this_node, rxDropPct);
+    NODE_WRITE_UNSIGNED( this_node, ecOptions);
+    NODE_WRITE_UNSIGNED( this_node, ecTailLen);
+    NODE_WRITE_UNSIGNED( this_node, sndRecLatency);
+    NODE_WRITE_UNSIGNED( this_node, sndPlayLatency);
+    NODE_WRITE_INT     ( this_node, jbInit);
+    NODE_WRITE_INT     ( this_node, jbMinPre);
+    NODE_WRITE_INT     ( this_node, jbMaxPre);
+    NODE_WRITE_INT     ( this_node, jbMax);
+    NODE_WRITE_INT     ( this_node, sndAutoCloseTime);
+    NODE_WRITE_BOOL    ( this_node, vidPreviewEnableNative);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void EpConfig::readObject(const ContainerNode &node) throw(Error)
+{
+    ContainerNode this_node = node.readContainer("EpConfig");
+    NODE_READ_OBJ( this_node, uaConfig);
+    NODE_READ_OBJ( this_node, logConfig);
+    NODE_READ_OBJ( this_node, medConfig);
+}
+
+void EpConfig::writeObject(ContainerNode &node) const throw(Error)
+{
+    ContainerNode this_node = node.writeNewContainer("EpConfig");
+    NODE_WRITE_OBJ( this_node, uaConfig);
+    NODE_WRITE_OBJ( this_node, logConfig);
+    NODE_WRITE_OBJ( this_node, medConfig);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+/* Class to post log to main thread */
+struct PendingLog : public PendingJob
+{
+    LogEntry entry;
+    virtual void execute(bool is_pending)
+    {
+	PJ_UNUSED_ARG(is_pending);
+	Endpoint::instance().utilLogWrite(entry);
+    }
+};
+
+///////////////////////////////////////////////////////////////////////////////
+/*
+ * Endpoint instance
+ */
+Endpoint::Endpoint()
+: writer(NULL), mainThreadOnly(false), mainThread(NULL), pendingJobSize(0)
+{
+    if (instance_) {
+	PJSUA2_RAISE_ERROR(PJ_EEXISTS);
+    }
+
+    instance_ = this;
+}
+
+Endpoint& Endpoint::instance() throw(Error)
+{
+    if (!instance_) {
+	PJSUA2_RAISE_ERROR(PJ_ENOTFOUND);
+    }
+    return *instance_;
+}
+
+Endpoint::~Endpoint()
+{
+    while (!pendingJobs.empty()) {
+	delete pendingJobs.front();
+	pendingJobs.pop_front();
+    }
+
+    while(mediaList.size() > 0) {
+	AudioMedia *cur_media = mediaList[0];
+	delete cur_media; /* this will remove itself from the list */
+    }
+
+    clearCodecInfoList();
+
+    try {
+	libDestroy();
+    } catch (Error &err) {
+	// Ignore
+	PJ_UNUSED_ARG(err);
+    }
+
+    instance_ = NULL;
+}
+
+void Endpoint::utilAddPendingJob(PendingJob *job)
+{
+    enum {
+	MAX_PENDING_JOBS = 1024
+    };
+
+    /* See if we can execute immediately */
+    if (!mainThreadOnly || pj_thread_this()==mainThread) {
+	job->execute(false);
+	delete job;
+	return;
+    }
+
+    if (pendingJobSize > MAX_PENDING_JOBS) {
+	enum { NUMBER_TO_DISCARD = 5 };
+
+	pj_enter_critical_section();
+	for (unsigned i=0; i<NUMBER_TO_DISCARD; ++i) {
+	    delete pendingJobs.back();
+	    pendingJobs.pop_back();
+	}
+
+	pendingJobSize -= NUMBER_TO_DISCARD;
+	pj_leave_critical_section();
+
+	utilLogWrite(1, THIS_FILE,
+	             "*** ERROR: Job queue full!! Jobs discarded!!! ***");
+    }
+
+    pj_enter_critical_section();
+    pendingJobs.push_back(job);
+    pendingJobSize++;
+    pj_leave_critical_section();
+}
+
+/* Handle log callback */
+void Endpoint::utilLogWrite(LogEntry &entry)
+{
+    if (mainThreadOnly && pj_thread_this() != mainThread) {
+	PendingLog *job = new PendingLog;
+	job->entry = entry;
+	utilAddPendingJob(job);
+    } else {
+	writer->write(entry);
+    }
+}
+
+/* Run pending jobs only in main thread */
+void Endpoint::performPendingJobs()
+{
+    if (pj_thread_this() != mainThread)
+	return;
+
+    if (pendingJobSize == 0)
+	return;
+
+    for (;;) {
+	PendingJob *job = NULL;
+
+	pj_enter_critical_section();
+	if (pendingJobSize != 0) {
+	    job = pendingJobs.front();
+	    pendingJobs.pop_front();
+	    pendingJobSize--;
+	}
+	pj_leave_critical_section();
+
+	if (job) {
+	    job->execute(true);
+	    delete job;
+	} else
+	    break;
+    }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+/*
+ * Endpoint static callbacks
+ */
+void Endpoint::logFunc(int level, const char *data, int len)
+{
+    Endpoint &ep = Endpoint::instance();
+
+    if (!ep.writer)
+	return;
+
+    LogEntry entry;
+    entry.level = level;
+    entry.msg = string(data, len);
+    entry.threadId = (long)pj_thread_this();
+    entry.threadName = string(pj_thread_get_name(pj_thread_this()));
+
+    ep.utilLogWrite(entry);
+}
+
+void Endpoint::stun_resolve_cb(const pj_stun_resolve_result *res)
+{
+    Endpoint &ep = Endpoint::instance();
+
+    if (!res)
+	return;
+
+    OnNatCheckStunServersCompleteParam prm;
+
+    prm.userData = res->token;
+    prm.status = res->status;
+    if (res->status == PJ_SUCCESS) {
+	char straddr[PJ_INET6_ADDRSTRLEN+10];
+
+	prm.name = string(res->name.ptr, res->name.slen);
+	pj_sockaddr_print(&res->addr, straddr, sizeof(straddr), 3);
+	prm.addr = straddr;
+    }
+
+    ep.onNatCheckStunServersComplete(prm);
+}
+
+void Endpoint::on_timer(pj_timer_heap_t *timer_heap,
+                        pj_timer_entry *entry)
+{
+    PJ_UNUSED_ARG(timer_heap);
+
+    Endpoint &ep = Endpoint::instance();
+    UserTimer *ut = (UserTimer*) entry->user_data;
+
+    if (ut->signature != TIMER_SIGNATURE)
+	return;
+
+    ep.onTimer(ut->prm);
+}
+
+void Endpoint::on_nat_detect(const pj_stun_nat_detect_result *res)
+{
+    Endpoint &ep = Endpoint::instance();
+
+    if (!res)
+	return;
+
+    OnNatDetectionCompleteParam prm;
+
+    prm.status = res->status;
+    prm.reason = res->status_text;
+    prm.natType = res->nat_type;
+    prm.natTypeName = res->nat_type_name;
+
+    ep.onNatDetectionComplete(prm);
+}
+
+void Endpoint::on_transport_state( pjsip_transport *tp,
+				   pjsip_transport_state state,
+				   const pjsip_transport_state_info *info)
+{
+    Endpoint &ep = Endpoint::instance();
+
+    OnTransportStateParam prm;
+
+    prm.hnd = (TransportHandle)tp;
+    prm.state = state;
+    prm.lastError = info ? info->status : PJ_SUCCESS;
+
+    ep.onTransportState(prm);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+/*
+ * Account static callbacks
+ */
+
+Account *Endpoint::lookupAcc(int acc_id, const char *op)
+{
+    Account *acc = Account::lookup(acc_id);
+    if (!acc) {
+	PJ_LOG(1,(THIS_FILE,
+		  "Error: cannot find Account instance for account id %d in "
+		  "%s", acc_id, op));
+    }
+
+    return acc;
+}
+
+Call *Endpoint::lookupCall(int call_id, const char *op)
+{
+    Call *call = Call::lookup(call_id);
+    if (!call) {
+	PJ_LOG(1,(THIS_FILE,
+		  "Error: cannot find Call instance for call id %d in "
+		  "%s", call_id, op));
+    }
+
+    return call;
+}
+
+void Endpoint::on_incoming_call(pjsua_acc_id acc_id, pjsua_call_id call_id,
+                                pjsip_rx_data *rdata)
+{
+    Account *acc = lookupAcc(acc_id, "on_incoming_call()");
+    if (!acc) {
+	pjsua_call_hangup(call_id, PJSIP_SC_INTERNAL_SERVER_ERROR, NULL, NULL);
+	return;
+    }
+
+    /* call callback */
+    OnIncomingCallParam prm;
+    prm.callId = call_id;
+    prm.rdata.fromPj(*rdata);
+
+    acc->onIncomingCall(prm);
+
+    /* disconnect if callback doesn't handle the call */
+    pjsua_call_info ci;
+
+    pjsua_call_get_info(call_id, &ci);
+    if (!pjsua_call_get_user_data(call_id) &&
+	ci.state != PJSIP_INV_STATE_DISCONNECTED)
+    {
+	pjsua_call_hangup(call_id, PJSIP_SC_INTERNAL_SERVER_ERROR, NULL, NULL);
+    }
+}
+
+void Endpoint::on_reg_started(pjsua_acc_id acc_id, pj_bool_t renew)
+{
+    Account *acc = lookupAcc(acc_id, "on_reg_started()");
+    if (!acc) {
+	return;
+    }
+
+    OnRegStartedParam prm;
+    prm.renew = PJ2BOOL(renew);
+    acc->onRegStarted(prm);
+}
+
+void Endpoint::on_reg_state2(pjsua_acc_id acc_id, pjsua_reg_info *info)
+{
+    Account *acc = lookupAcc(acc_id, "on_reg_state2()");
+    if (!acc) {
+	return;
+    }
+
+    OnRegStateParam prm;
+    prm.status		= info->cbparam->status;
+    prm.code 		= (pjsip_status_code) info->cbparam->code;
+    prm.reason		= pj2Str(info->cbparam->reason);
+    if (info->cbparam->rdata)
+	prm.rdata.fromPj(*info->cbparam->rdata);
+    prm.expiration	= info->cbparam->expiration;
+
+    acc->onRegState(prm);
+}
+
+void Endpoint::on_incoming_subscribe(pjsua_acc_id acc_id,
+                                     pjsua_srv_pres *srv_pres,
+                                     pjsua_buddy_id buddy_id,
+                                     const pj_str_t *from,
+                                     pjsip_rx_data *rdata,
+                                     pjsip_status_code *code,
+                                     pj_str_t *reason,
+                                     pjsua_msg_data *msg_data)
+{
+    PJ_UNUSED_ARG(buddy_id);
+    PJ_UNUSED_ARG(srv_pres);
+
+    Account *acc = lookupAcc(acc_id, "on_incoming_subscribe()");
+    if (!acc) {
+	/* default behavior should apply */
+	return;
+    }
+
+    OnIncomingSubscribeParam prm;
+    prm.srvPres		= srv_pres;
+    prm.fromUri 	= pj2Str(*from);
+    prm.rdata.fromPj(*rdata);
+    prm.code		= *code;
+    prm.reason		= pj2Str(*reason);
+    prm.txOption.fromPj(*msg_data);
+
+    acc->onIncomingSubscribe(prm);
+
+    *code = prm.code;
+    acc->tmpReason = prm.reason;
+    *reason = str2Pj(acc->tmpReason);
+    prm.txOption.toPj(*msg_data);
+}
+
+void Endpoint::on_pager2(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 *body,
+                         pjsip_rx_data *rdata,
+                         pjsua_acc_id acc_id)
+{
+    OnInstantMessageParam prm;
+    prm.fromUri		= pj2Str(*from);
+    prm.toUri		= pj2Str(*to);
+    prm.contactUri	= pj2Str(*contact);
+    prm.contentType	= pj2Str(*mime_type);
+    prm.msgBody		= pj2Str(*body);
+    prm.rdata.fromPj(*rdata);
+
+    if (call_id != PJSUA_INVALID_ID) {
+	Call *call = lookupCall(call_id, "on_pager2()");
+	if (!call) {
+	    /* Ignored */
+	    return;
+	}
+
+	call->onInstantMessage(prm);
+    } else {
+	Account *acc = lookupAcc(acc_id, "on_pager2()");
+	if (!acc) {
+	    /* Ignored */
+	    return;
+	}
+
+	acc->onInstantMessage(prm);
+    }
+}
+
+void Endpoint::on_pager_status2( pjsua_call_id call_id,
+				 const pj_str_t *to,
+				 const pj_str_t *body,
+				 void *user_data,
+				 pjsip_status_code status,
+				 const pj_str_t *reason,
+				 pjsip_tx_data *tdata,
+				 pjsip_rx_data *rdata,
+				 pjsua_acc_id acc_id)
+{
+    PJ_UNUSED_ARG(tdata);
+
+    OnInstantMessageStatusParam prm;
+    prm.userData	= user_data;
+    prm.toUri		= pj2Str(*to);
+    prm.msgBody		= pj2Str(*body);
+    prm.code		= status;
+    prm.reason		= pj2Str(*reason);
+    if (rdata)
+	prm.rdata.fromPj(*rdata);
+
+    if (call_id != PJSUA_INVALID_ID) {
+	Call *call = lookupCall(call_id, "on_pager_status2()");
+	if (!call) {
+	    /* Ignored */
+	    return;
+	}
+
+	call->onInstantMessageStatus(prm);
+    } else {
+	Account *acc = lookupAcc(acc_id, "on_pager_status2()");
+	if (!acc) {
+	    /* Ignored */
+	    return;
+	}
+
+	acc->onInstantMessageStatus(prm);
+    }
+}
+
+void Endpoint::on_typing2( 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,
+			   pjsip_rx_data *rdata,
+			   pjsua_acc_id acc_id)
+{
+    OnTypingIndicationParam prm;
+    prm.fromUri		= pj2Str(*from);
+    prm.toUri		= pj2Str(*to);
+    prm.contactUri	= pj2Str(*contact);
+    prm.isTyping	= is_typing != 0;
+    prm.rdata.fromPj(*rdata);
+
+    if (call_id != PJSUA_INVALID_ID) {
+	Call *call = lookupCall(call_id, "on_typing2()");
+	if (!call) {
+	    /* Ignored */
+	    return;
+	}
+
+	call->onTypingIndication(prm);
+    } else {
+	Account *acc = lookupAcc(acc_id, "on_typing2()");
+	if (!acc) {
+	    /* Ignored */
+	    return;
+	}
+
+	acc->onTypingIndication(prm);
+    }
+}
+
+void Endpoint::on_mwi_info(pjsua_acc_id acc_id,
+                           pjsua_mwi_info *mwi_info)
+{
+    OnMwiInfoParam prm;
+    prm.state	= pjsip_evsub_get_state(mwi_info->evsub);
+    prm.rdata.fromPj(*mwi_info->rdata);
+
+    Account *acc = lookupAcc(acc_id, "on_mwi_info()");
+    if (!acc) {
+	/* Ignored */
+	return;
+    }
+
+    acc->onMwiInfo(prm);
+}
+
+void Endpoint::on_buddy_state(pjsua_buddy_id buddy_id)
+{
+    Buddy *buddy = (Buddy*)pjsua_buddy_get_user_data(buddy_id);
+    if (!buddy || !buddy->isValid()) {
+	/* Ignored */
+	return;
+    }
+
+    buddy->onBuddyState();
+}
+
+// Call callbacks
+void Endpoint::on_call_state(pjsua_call_id call_id, pjsip_event *e)
+{
+    Call *call = Call::lookup(call_id);
+    if (!call) {
+	return;
+    }
+    
+    OnCallStateParam prm;
+    prm.e.fromPj(*e);
+    
+    call->processStateChange(prm);
+    /* If the state is DISCONNECTED, call may have already been deleted
+     * by the application in the callback, so do not access it anymore here.
+     */
+}
+
+void Endpoint::on_call_tsx_state(pjsua_call_id call_id,
+                                 pjsip_transaction *tsx,
+                                 pjsip_event *e)
+{
+    PJ_UNUSED_ARG(tsx);
+
+    Call *call = Call::lookup(call_id);
+    if (!call) {
+	return;
+    }
+    
+    OnCallTsxStateParam prm;
+    prm.e.fromPj(*e);
+    
+    call->onCallTsxState(prm);
+}
+
+void Endpoint::on_call_media_state(pjsua_call_id call_id)
+{
+    Call *call = Call::lookup(call_id);
+    if (!call) {
+	return;
+    }
+
+    OnCallMediaStateParam prm;
+    call->processMediaUpdate(prm);
+}
+
+void Endpoint::on_call_sdp_created(pjsua_call_id call_id,
+                                   pjmedia_sdp_session *sdp,
+                                   pj_pool_t *pool,
+                                   const pjmedia_sdp_session *rem_sdp)
+{
+    Call *call = Call::lookup(call_id);
+    if (!call) {
+	return;
+    }
+    
+    OnCallSdpCreatedParam prm;
+    string orig_sdp;
+    
+    prm.sdp.fromPj(*sdp);
+    orig_sdp = prm.sdp.wholeSdp;
+    if (rem_sdp)
+        prm.remSdp.fromPj(*rem_sdp);
+    
+    call->onCallSdpCreated(prm);
+    
+    /* Check if application modifies the SDP */
+    if (orig_sdp != prm.sdp.wholeSdp) {
+        pjmedia_sdp_parse(pool, (char*)prm.sdp.wholeSdp.c_str(),
+                          prm.sdp.wholeSdp.size(), &sdp);
+    }
+}
+
+void Endpoint::on_stream_created(pjsua_call_id call_id,
+                                 pjmedia_stream *strm,
+                                 unsigned stream_idx,
+                                 pjmedia_port **p_port)
+{
+    Call *call = Call::lookup(call_id);
+    if (!call) {
+	return;
+    }
+    
+    OnStreamCreatedParam prm;
+    prm.stream = strm;
+    prm.streamIdx = stream_idx;
+    prm.pPort = (void *)*p_port;
+    
+    call->onStreamCreated(prm);
+    
+    if (prm.pPort != (void *)*p_port)
+        *p_port = (pjmedia_port *)prm.pPort;
+}
+
+void Endpoint::on_stream_destroyed(pjsua_call_id call_id,
+                                   pjmedia_stream *strm,
+                                   unsigned stream_idx)
+{
+    Call *call = Call::lookup(call_id);
+    if (!call) {
+	return;
+    }
+    
+    OnStreamDestroyedParam prm;
+    prm.stream = strm;
+    prm.streamIdx = stream_idx;
+    
+    call->onStreamDestroyed(prm);
+}
+
+struct PendingOnDtmfDigitCallback : public PendingJob
+{
+    int call_id;
+    OnDtmfDigitParam prm;
+
+    virtual void execute(bool is_pending)
+    {
+	PJ_UNUSED_ARG(is_pending);
+
+	Call *call = Call::lookup(call_id);
+	if (!call)
+	    return;
+
+	call->onDtmfDigit(prm);
+    }
+};
+
+void Endpoint::on_dtmf_digit(pjsua_call_id call_id, int digit)
+{
+    Call *call = Call::lookup(call_id);
+    if (!call) {
+	return;
+    }
+    
+    PendingOnDtmfDigitCallback *job = new PendingOnDtmfDigitCallback;
+    job->call_id = call_id;
+    char buf[10];
+    pj_ansi_sprintf(buf, "%c", digit);
+    job->prm.digit = (string)buf;
+    
+    Endpoint::instance().utilAddPendingJob(job);
+}
+
+void Endpoint::on_call_transfer_request2(pjsua_call_id call_id,
+                                         const pj_str_t *dst,
+                                         pjsip_status_code *code,
+                                         pjsua_call_setting *opt)
+{
+    Call *call = Call::lookup(call_id);
+    if (!call) {
+	return;
+    }
+    
+    OnCallTransferRequestParam prm;
+    prm.dstUri = pj2Str(*dst);
+    prm.statusCode = *code;
+    prm.opt.fromPj(*opt);
+    
+    call->onCallTransferRequest(prm);
+    
+    *code = prm.statusCode;
+    *opt = prm.opt.toPj();
+}
+
+void Endpoint::on_call_transfer_status(pjsua_call_id call_id,
+                                       int st_code,
+                                       const pj_str_t *st_text,
+                                       pj_bool_t final,
+                                       pj_bool_t *p_cont)
+{
+    Call *call = Call::lookup(call_id);
+    if (!call) {
+	return;
+    }
+    
+    OnCallTransferStatusParam prm;
+    prm.statusCode = (pjsip_status_code)st_code;
+    prm.reason = pj2Str(*st_text);
+    prm.finalNotify = PJ2BOOL(final);
+    prm.cont = PJ2BOOL(*p_cont);
+    
+    call->onCallTransferStatus(prm);
+    
+    *p_cont = prm.cont;
+}
+
+void Endpoint::on_call_replace_request2(pjsua_call_id call_id,
+                                        pjsip_rx_data *rdata,
+                                        int *st_code,
+                                        pj_str_t *st_text,
+                                        pjsua_call_setting *opt)
+{
+    Call *call = Call::lookup(call_id);
+    if (!call) {
+	return;
+    }
+    
+    OnCallReplaceRequestParam prm;
+    prm.rdata.fromPj(*rdata);
+    prm.statusCode = (pjsip_status_code)*st_code;
+    prm.reason = pj2Str(*st_text);
+    prm.opt.fromPj(*opt);
+    
+    call->onCallReplaceRequest(prm);
+    
+    *st_code = prm.statusCode;
+    *st_text = str2Pj(prm.reason);
+    *opt = prm.opt.toPj();
+}
+
+void Endpoint::on_call_replaced(pjsua_call_id old_call_id,
+                                pjsua_call_id new_call_id)
+{
+    Call *call = Call::lookup(old_call_id);
+    if (!call) {
+	return;
+    }
+    
+    OnCallReplacedParam prm;
+    prm.newCallId = new_call_id;
+    
+    call->onCallReplaced(prm);
+}
+
+void Endpoint::on_call_rx_offer(pjsua_call_id call_id,
+                                const pjmedia_sdp_session *offer,
+                                void *reserved,
+                                pjsip_status_code *code,
+                                pjsua_call_setting *opt)
+{
+    PJ_UNUSED_ARG(reserved);
+
+    Call *call = Call::lookup(call_id);
+    if (!call) {
+	return;
+    }
+    
+    OnCallRxOfferParam prm;
+    prm.offer.fromPj(*offer);
+    prm.statusCode = *code;
+    prm.opt.fromPj(*opt);
+    
+    call->onCallRxOffer(prm);
+    
+    *code = prm.statusCode;
+    *opt = prm.opt.toPj();
+}
+
+pjsip_redirect_op Endpoint::on_call_redirected(pjsua_call_id call_id,
+                                               const pjsip_uri *target,
+                                               const pjsip_event *e)
+{
+    Call *call = Call::lookup(call_id);
+    if (!call) {
+	return PJSIP_REDIRECT_STOP;
+    }
+    
+    OnCallRedirectedParam prm;
+    char uristr[PJSIP_MAX_URL_SIZE];
+    int len = pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, target, uristr,
+                              sizeof(uristr));
+    if (len < 1) {
+        pj_ansi_strcpy(uristr, "--URI too long--");
+    }
+    prm.targetUri = string(uristr);
+    if (e)
+        prm.e.fromPj(*e);
+    else
+        prm.e.type = PJSIP_EVENT_UNKNOWN;
+    
+    return call->onCallRedirected(prm);
+}
+
+
+struct PendingOnMediaTransportCallback : public PendingJob
+{
+    int call_id;
+    OnCallMediaTransportStateParam prm;
+
+    virtual void execute(bool is_pending)
+    {
+	PJ_UNUSED_ARG(is_pending);
+
+	Call *call = Call::lookup(call_id);
+	if (!call)
+	    return;
+
+	call->onCallMediaTransportState(prm);
+    }
+};
+
+pj_status_t
+Endpoint::on_call_media_transport_state(pjsua_call_id call_id,
+                                        const pjsua_med_tp_state_info *info)
+{
+    Call *call = Call::lookup(call_id);
+    if (!call) {
+	return PJ_SUCCESS;
+    }
+
+    PendingOnMediaTransportCallback *job = new PendingOnMediaTransportCallback;
+    
+    job->call_id = call_id;
+    job->prm.medIdx = info->med_idx;
+    job->prm.state = info->state;
+    job->prm.status = info->status;
+    job->prm.sipErrorCode = info->sip_err_code;
+    
+    Endpoint::instance().utilAddPendingJob(job);
+
+    return PJ_SUCCESS;
+}
+
+struct PendingOnMediaEventCallback : public PendingJob
+{
+    int call_id;
+    OnCallMediaEventParam prm;
+
+    virtual void execute(bool is_pending)
+    {
+	Call *call = Call::lookup(call_id);
+	if (!call)
+	    return;
+
+	if (is_pending) {
+	    /* Can't do this anymore, pointer is invalid */
+	    prm.ev.pjMediaEvent = NULL;
+	}
+
+	call->onCallMediaEvent(prm);
+    }
+};
+
+void Endpoint::on_call_media_event(pjsua_call_id call_id,
+                                   unsigned med_idx,
+                                   pjmedia_event *event)
+{
+    Call *call = Call::lookup(call_id);
+    if (!call) {
+	return;
+    }
+    
+    PendingOnMediaEventCallback *job = new PendingOnMediaEventCallback;
+
+    job->call_id = call_id;
+    job->prm.medIdx = med_idx;
+    job->prm.ev.fromPj(*event);
+    
+    Endpoint::instance().utilAddPendingJob(job);
+}
+
+pjmedia_transport*
+Endpoint::on_create_media_transport(pjsua_call_id call_id,
+                                    unsigned media_idx,
+                                    pjmedia_transport *base_tp,
+                                    unsigned flags)
+{
+    Call *call = Call::lookup(call_id);
+    if (!call) {
+	return base_tp;
+    }
+    
+    OnCreateMediaTransportParam prm;
+    prm.mediaIdx = media_idx;
+    prm.mediaTp = base_tp;
+    prm.flags = flags;
+    
+    call->onCreateMediaTransport(prm);
+    
+    return (pjmedia_transport *)prm.mediaTp;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+/*
+ * Endpoint library operations
+ */
+Version Endpoint::libVersion() const
+{
+    Version ver;
+    ver.major = PJ_VERSION_NUM_MAJOR;
+    ver.minor = PJ_VERSION_NUM_MINOR;
+    ver.rev = PJ_VERSION_NUM_REV;
+    ver.suffix = PJ_VERSION_NUM_EXTRA;
+    ver.full = pj_get_version();
+    ver.numeric = PJ_VERSION_NUM;
+    return ver;
+}
+
+void Endpoint::libCreate() throw(Error)
+{
+    PJSUA2_CHECK_EXPR( pjsua_create() );
+    mainThread = pj_thread_this();
+}
+
+pjsua_state Endpoint::libGetState() const
+{
+    return pjsua_get_state();
+}
+
+void Endpoint::libInit(const EpConfig &prmEpConfig) throw(Error)
+{
+    pjsua_config ua_cfg;
+    pjsua_logging_config log_cfg;
+    pjsua_media_config med_cfg;
+
+    ua_cfg = prmEpConfig.uaConfig.toPj();
+    log_cfg = prmEpConfig.logConfig.toPj();
+    med_cfg = prmEpConfig.medConfig.toPj();
+
+    /* Setup log callback */
+    if (prmEpConfig.logConfig.writer) {
+	this->writer = prmEpConfig.logConfig.writer;
+	log_cfg.cb = &Endpoint::logFunc;
+    }
+    mainThreadOnly = prmEpConfig.uaConfig.mainThreadOnly;
+
+    /* Setup UA callbacks */
+    pj_bzero(&ua_cfg.cb, sizeof(ua_cfg.cb));
+    ua_cfg.cb.on_nat_detect 	= &Endpoint::on_nat_detect;
+    ua_cfg.cb.on_transport_state = &Endpoint::on_transport_state;
+
+    ua_cfg.cb.on_incoming_call	= &Endpoint::on_incoming_call;
+    ua_cfg.cb.on_reg_started	= &Endpoint::on_reg_started;
+    ua_cfg.cb.on_reg_state2	= &Endpoint::on_reg_state2;
+    ua_cfg.cb.on_incoming_subscribe = &Endpoint::on_incoming_subscribe;
+    ua_cfg.cb.on_pager2		= &Endpoint::on_pager2;
+    ua_cfg.cb.on_pager_status2	= &Endpoint::on_pager_status2;
+    ua_cfg.cb.on_typing2	= &Endpoint::on_typing2;
+    ua_cfg.cb.on_mwi_info	= &Endpoint::on_mwi_info;
+    ua_cfg.cb.on_buddy_state	= &Endpoint::on_buddy_state;
+
+    /* Call callbacks */
+    ua_cfg.cb.on_call_state             = &Endpoint::on_call_state;
+    ua_cfg.cb.on_call_tsx_state         = &Endpoint::on_call_tsx_state;
+    ua_cfg.cb.on_call_media_state       = &Endpoint::on_call_media_state;
+    ua_cfg.cb.on_call_sdp_created       = &Endpoint::on_call_sdp_created;
+    ua_cfg.cb.on_stream_created         = &Endpoint::on_stream_created;
+    ua_cfg.cb.on_stream_destroyed       = &Endpoint::on_stream_destroyed;
+    ua_cfg.cb.on_dtmf_digit             = &Endpoint::on_dtmf_digit;
+    ua_cfg.cb.on_call_transfer_request2 = &Endpoint::on_call_transfer_request2;
+    ua_cfg.cb.on_call_transfer_status   = &Endpoint::on_call_transfer_status;
+    ua_cfg.cb.on_call_replace_request2  = &Endpoint::on_call_replace_request2;
+    ua_cfg.cb.on_call_replaced          = &Endpoint::on_call_replaced;
+    ua_cfg.cb.on_call_rx_offer          = &Endpoint::on_call_rx_offer;
+    ua_cfg.cb.on_call_redirected        = &Endpoint::on_call_redirected;
+    ua_cfg.cb.on_call_media_transport_state =
+        &Endpoint::on_call_media_transport_state;
+    ua_cfg.cb.on_call_media_event       = &Endpoint::on_call_media_event;
+    ua_cfg.cb.on_create_media_transport = &Endpoint::on_create_media_transport;
+
+    /* Init! */
+    PJSUA2_CHECK_EXPR( pjsua_init(&ua_cfg, &log_cfg, &med_cfg) );
+}
+
+void Endpoint::libStart() throw(Error)
+{
+    PJSUA2_CHECK_EXPR(pjsua_start());
+}
+
+void Endpoint::libRegisterWorkerThread(const string &name) throw(Error)
+{
+    PJSUA2_CHECK_EXPR(pjsua_register_worker_thread(name.c_str()));
+}
+
+void Endpoint::libStopWorkerThreads()
+{
+    pjsua_stop_worker_threads();
+}
+
+int Endpoint::libHandleEvents(unsigned msec_timeout)
+{
+    performPendingJobs();
+    return pjsua_handle_events(msec_timeout);
+}
+
+void Endpoint::libDestroy(unsigned flags) throw(Error)
+{
+    pj_status_t status;
+
+    status = pjsua_destroy2(flags);
+
+    delete this->writer;
+    this->writer = NULL;
+
+    if (pj_log_get_log_func() == &Endpoint::logFunc) {
+	pj_log_set_log_func(NULL);
+    }
+
+    PJSUA2_CHECK_RAISE_ERROR(status);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+/*
+ * Endpoint Utilities
+ */
+string Endpoint::utilStrError(pj_status_t prmErr)
+{
+    char errmsg[PJ_ERR_MSG_SIZE];
+    pj_strerror(prmErr, errmsg, sizeof(errmsg));
+    return errmsg;
+}
+
+static void ept_log_write(int level, const char *sender,
+                          const char *format, ...)
+{
+    va_list arg;
+    va_start(arg, format);
+    pj_log(sender, level, format, arg );
+    va_end(arg);
+}
+
+void Endpoint::utilLogWrite(int prmLevel,
+			    const string &prmSender,
+			    const string &prmMsg)
+{
+    ept_log_write(prmLevel, prmSender.c_str(), "%s", prmMsg.c_str());
+}
+
+pj_status_t Endpoint::utilVerifySipUri(const string &prmUri)
+{
+    return pjsua_verify_sip_url(prmUri.c_str());
+}
+
+pj_status_t Endpoint::utilVerifyUri(const string &prmUri)
+{
+    return pjsua_verify_url(prmUri.c_str());
+}
+
+Token Endpoint::utilTimerSchedule(unsigned prmMsecDelay,
+                                  Token prmUserData) throw (Error)
+{
+    UserTimer *ut;
+    pj_time_val delay;
+    pj_status_t status;
+
+    ut = new UserTimer;
+    ut->signature = TIMER_SIGNATURE;
+    ut->prm.msecDelay = prmMsecDelay;
+    ut->prm.userData = prmUserData;
+    pj_timer_entry_init(&ut->entry, 1, ut, &Endpoint::on_timer);
+
+    delay.sec = 0;
+    delay.msec = prmMsecDelay;
+    pj_time_val_normalize(&delay);
+
+    status = pjsua_schedule_timer(&ut->entry, &delay);
+    if (status != PJ_SUCCESS) {
+	delete ut;
+	PJSUA2_CHECK_RAISE_ERROR(status);
+    }
+
+    return (Token)ut;
+}
+
+void Endpoint::utilTimerCancel(Token prmTimerToken)
+{
+    UserTimer *ut = (UserTimer*)(void*)prmTimerToken;
+
+    if (ut->signature != TIMER_SIGNATURE) {
+	PJ_LOG(1,(THIS_FILE,
+		  "Invalid timer token in Endpoint::utilTimerCancel()"));
+	return;
+    }
+
+    ut->entry.id = 0;
+    ut->signature = 0xFFFFFFFE;
+    pjsua_cancel_timer(&ut->entry);
+
+    delete ut;
+}
+
+IntVector Endpoint::utilSslGetAvailableCiphers() throw (Error)
+{
+#if PJ_HAS_SSL_SOCK
+    pj_ssl_cipher ciphers[64];
+    unsigned count = PJ_ARRAY_SIZE(ciphers);
+
+    PJSUA2_CHECK_EXPR( pj_ssl_cipher_get_availables(ciphers, &count) );
+
+    return IntVector(ciphers, ciphers + count);
+#else
+    return IntVector();
+#endif
+}
+
+///////////////////////////////////////////////////////////////////////////////
+/*
+ * Endpoint NAT operations
+ */
+void Endpoint::natDetectType(void) throw(Error)
+{
+    PJSUA2_CHECK_EXPR( pjsua_detect_nat_type() );
+}
+
+pj_stun_nat_type Endpoint::natGetType() throw(Error)
+{
+    pj_stun_nat_type type;
+
+    PJSUA2_CHECK_EXPR( pjsua_get_nat_type(&type) );
+
+    return type;
+}
+
+void Endpoint::natCheckStunServers(const StringVector &servers,
+				   bool wait,
+				   Token token) throw(Error)
+{
+    pj_str_t srv[MAX_STUN_SERVERS];
+    unsigned i, count = 0;
+
+    for (i=0; i<servers.size() && i<MAX_STUN_SERVERS; ++i) {
+	srv[count].ptr = (char*)servers[i].c_str();
+	srv[count].slen = servers[i].size();
+	++count;
+    }
+
+    PJSUA2_CHECK_EXPR(pjsua_resolve_stun_servers(count, srv, wait, token,
+                                                 &Endpoint::stun_resolve_cb) );
+}
+
+void Endpoint::natCancelCheckStunServers(Token token,
+                                         bool notify_cb) throw(Error)
+{
+    PJSUA2_CHECK_EXPR( pjsua_cancel_stun_resolution(token, notify_cb) );
+}
+
+///////////////////////////////////////////////////////////////////////////////
+/*
+ * Transport API
+ */
+TransportId Endpoint::transportCreate(pjsip_transport_type_e type,
+                                      const TransportConfig &cfg) throw(Error)
+{
+    pjsua_transport_config tcfg;
+    pjsua_transport_id tid;
+
+    tcfg = cfg.toPj();
+    PJSUA2_CHECK_EXPR( pjsua_transport_create(type,
+                                              &tcfg, &tid) );
+
+    return tid;
+}
+
+IntVector Endpoint::transportEnum() throw(Error)
+{
+    pjsua_transport_id tids[32];
+    unsigned count = PJ_ARRAY_SIZE(tids);
+
+    PJSUA2_CHECK_EXPR( pjsua_enum_transports(tids, &count) );
+
+    return IntVector(tids, tids+count);
+}
+
+TransportInfo Endpoint::transportGetInfo(TransportId id) throw(Error)
+{
+    pjsua_transport_info pj_tinfo;
+    TransportInfo tinfo;
+
+    PJSUA2_CHECK_EXPR( pjsua_transport_get_info(id, &pj_tinfo) );
+    tinfo.fromPj(pj_tinfo);
+
+    return tinfo;
+}
+
+void Endpoint::transportSetEnable(TransportId id, bool enabled) throw(Error)
+{
+    PJSUA2_CHECK_EXPR( pjsua_transport_set_enable(id, enabled) );
+}
+
+void Endpoint::transportClose(TransportId id) throw(Error)
+{
+    PJSUA2_CHECK_EXPR( pjsua_transport_close(id, PJ_FALSE) );
+}
+
+///////////////////////////////////////////////////////////////////////////////
+/*
+ * Call operations
+ */
+
+void Endpoint::hangupAllCalls(void)
+{
+    pjsua_call_hangup_all();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+/*
+ * Media API
+ */
+unsigned Endpoint::mediaMaxPorts() const
+{
+    return pjsua_conf_get_max_ports();
+}
+
+unsigned Endpoint::mediaActivePorts() const
+{
+    return pjsua_conf_get_active_ports();
+}
+
+const AudioMediaVector &Endpoint::mediaEnumPorts() const throw(Error)
+{
+    return mediaList;
+}
+
+void Endpoint::mediaAdd(AudioMedia &media)
+{
+    if (mediaExists(media))
+	return;
+
+    mediaList.push_back(&media);
+}
+
+void Endpoint::mediaRemove(AudioMedia &media)
+{
+    AudioMediaVector::iterator it = std::find(mediaList.begin(),
+					      mediaList.end(),
+					      &media);
+
+    if (it != mediaList.end())
+	mediaList.erase(it);
+
+}
+
+bool Endpoint::mediaExists(const AudioMedia &media) const
+{
+    AudioMediaVector::const_iterator it = std::find(mediaList.begin(),
+						    mediaList.end(),
+						    &media);
+
+    return (it != mediaList.end());
+}
+
+AudDevManager &Endpoint::audDevManager()
+{
+    return audioDevMgr;
+}
+
+/*
+ * Codec operations.
+ */
+const CodecInfoVector &Endpoint::codecEnum() throw(Error)
+{
+    pjsua_codec_info pj_codec[MAX_CODEC_NUM];
+    unsigned count = 0;
+
+    PJSUA2_CHECK_EXPR( pjsua_enum_codecs(pj_codec, &count) );
+
+    pj_enter_critical_section();
+    clearCodecInfoList();
+    for (unsigned i=0;(i<count && i<MAX_CODEC_NUM);++i) {
+	CodecInfo *codec_info = new CodecInfo;
+
+	codec_info->fromPj(pj_codec[i]);
+	codecInfoList.push_back(codec_info);
+    }
+    pj_leave_critical_section();
+    return codecInfoList;
+}
+
+void Endpoint::codecSetPriority(const string &codec_id,
+			        pj_uint8_t priority) throw(Error)
+{
+    pj_str_t codec_str = str2Pj(codec_id);
+    PJSUA2_CHECK_EXPR( pjsua_codec_set_priority(&codec_str, priority) );
+}
+
+CodecParam Endpoint::codecGetParam(const string &codec_id) const throw(Error)
+{
+    pjmedia_codec_param *pj_param = NULL;
+    pj_str_t codec_str = str2Pj(codec_id);
+
+    PJSUA2_CHECK_EXPR( pjsua_codec_get_param(&codec_str, pj_param) );
+
+    return pj_param;
+}
+
+void Endpoint::codecSetParam(const string &codec_id,
+			     const CodecParam param) throw(Error)
+{
+    pj_str_t codec_str = str2Pj(codec_id);
+    pjmedia_codec_param *pj_param = (pjmedia_codec_param*)param;
+
+    PJSUA2_CHECK_EXPR( pjsua_codec_set_param(&codec_str, pj_param) );
+}
+
+void Endpoint::clearCodecInfoList()
+{
+    for (unsigned i=0;i<codecInfoList.size();++i) {
+	delete codecInfoList[i];
+    }
+    codecInfoList.clear();
+}