blob: 4b11bdae409a4a3156b50e47fe735ccbc2c33f3d [file] [log] [blame]
/* $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();
}