2706 lines
78 KiB
C++
2706 lines
78 KiB
C++
/*
|
|
* 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 <pjsua-lib/pjsua_internal.h> /* For retrieving pjsua threads */
|
|
|
|
#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_;
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
TlsInfo::TlsInfo()
|
|
: established(false),
|
|
protocol(0),
|
|
cipher(PJ_TLS_UNKNOWN_CIPHER),
|
|
verifyStatus(0),
|
|
empty(true)
|
|
{
|
|
}
|
|
|
|
bool TlsInfo::isEmpty() const
|
|
{
|
|
return empty;
|
|
}
|
|
|
|
void TlsInfo::fromPj(const pjsip_tls_state_info &info)
|
|
{
|
|
#if defined(PJ_HAS_SSL_SOCK) && PJ_HAS_SSL_SOCK != 0
|
|
pj_ssl_sock_info *ssock_info = info.ssl_sock_info;
|
|
char straddr[PJ_INET6_ADDRSTRLEN+10];
|
|
const char *verif_msgs[32];
|
|
const char *cipher_name;
|
|
unsigned verif_msg_cnt;
|
|
|
|
empty = false;
|
|
established = PJ2BOOL(ssock_info->established);
|
|
protocol = ssock_info->proto;
|
|
cipher = ssock_info->cipher;
|
|
cipher_name = pj_ssl_cipher_name(ssock_info->cipher);
|
|
if (cipher_name) {
|
|
cipherName = cipher_name;
|
|
} else {
|
|
char tmp[32];
|
|
pj_ansi_snprintf(tmp, sizeof(tmp), "Cipher 0x%x", cipher);
|
|
cipherName = tmp;
|
|
}
|
|
pj_sockaddr_print(&ssock_info->local_addr, straddr, sizeof(straddr), 3);
|
|
localAddr = straddr;
|
|
pj_sockaddr_print(&ssock_info->remote_addr, straddr, sizeof(straddr),3);
|
|
remoteAddr = straddr;
|
|
verifyStatus = ssock_info->verify_status;
|
|
if (ssock_info->local_cert_info)
|
|
localCertInfo.fromPj(*ssock_info->local_cert_info);
|
|
if (ssock_info->remote_cert_info)
|
|
remoteCertInfo.fromPj(*ssock_info->remote_cert_info);
|
|
|
|
/* Dump server TLS certificate verification result */
|
|
verif_msg_cnt = PJ_ARRAY_SIZE(verif_msgs);
|
|
pj_ssl_cert_get_verify_status_strings(ssock_info->verify_status,
|
|
verif_msgs, &verif_msg_cnt);
|
|
for (unsigned i = 0; i < verif_msg_cnt; ++i) {
|
|
verifyMsgs.push_back(verif_msgs[i]);
|
|
}
|
|
#else
|
|
PJ_UNUSED_ARG(info);
|
|
#endif
|
|
}
|
|
|
|
SslCertInfo::SslCertInfo()
|
|
: version(0xFF),
|
|
validityGmt(false),
|
|
empty(true)
|
|
{
|
|
}
|
|
|
|
bool SslCertInfo::isEmpty() const
|
|
{
|
|
return empty;
|
|
}
|
|
|
|
void SslCertInfo::fromPj(const pj_ssl_cert_info &info)
|
|
{
|
|
empty = false;
|
|
version = info.version;
|
|
pj_memcpy(serialNo, info.serial_no, sizeof(info.serial_no));
|
|
subjectCn = pj2Str(info.subject.cn);
|
|
subjectInfo = pj2Str(info.subject.info);
|
|
issuerCn = pj2Str(info.issuer.cn);
|
|
issuerInfo = pj2Str(info.issuer.info);
|
|
validityStart.fromPj(info.validity.start);
|
|
validityEnd.fromPj(info.validity.end);
|
|
validityGmt = PJ2BOOL(info.validity.gmt);
|
|
raw = pj2Str(info.raw);
|
|
|
|
for (unsigned i = 0; i < info.subj_alt_name.cnt; i++) {
|
|
SslCertName cname;
|
|
cname.type = info.subj_alt_name.entry[i].type;
|
|
cname.name = pj2Str(info.subj_alt_name.entry[i].name);
|
|
subjectAltName.push_back(cname);
|
|
}
|
|
}
|
|
|
|
void DigestCredential::fromPj(const pjsip_digest_credential &prm)
|
|
{
|
|
realm = pj2Str(prm.realm);
|
|
pjsip_param *p = (pjsip_param*)prm.other_param.next;
|
|
while (p != &prm.other_param) {
|
|
otherParam[pj2Str(p->name)] = pj2Str(p->value);
|
|
p = p->next;
|
|
}
|
|
username = pj2Str(prm.username);
|
|
nonce = pj2Str(prm.nonce);
|
|
uri = pj2Str(prm.uri);
|
|
response = pj2Str(prm.response);
|
|
algorithm = pj2Str(prm.algorithm);
|
|
cnonce = pj2Str(prm.cnonce);
|
|
opaque = pj2Str(prm.opaque);
|
|
qop = pj2Str(prm.qop);
|
|
nc = pj2Str(prm.nc);
|
|
}
|
|
|
|
pjsip_digest_credential DigestCredential::toPj() const
|
|
{
|
|
pjsip_digest_credential credentials;
|
|
pj_list_init(&credentials.other_param);
|
|
credentials.realm = str2Pj(realm);
|
|
credentials.username = str2Pj(username);
|
|
for (std::map<std::string, std::string>::const_iterator it = otherParam.begin();
|
|
it != otherParam.end(); ++it) {
|
|
pjsip_param other_param;
|
|
other_param.name = str2Pj(it->first);
|
|
other_param.value = str2Pj(it->second);
|
|
pj_list_push_back(&credentials.other_param, &other_param);
|
|
}
|
|
credentials.nonce = str2Pj(nonce);
|
|
credentials.uri = str2Pj(uri);
|
|
credentials.response = str2Pj(response);
|
|
credentials.algorithm = str2Pj(algorithm);
|
|
credentials.cnonce = str2Pj(cnonce);
|
|
credentials.opaque = str2Pj(opaque);
|
|
credentials.qop = str2Pj(qop);
|
|
credentials.nc = str2Pj(nc);
|
|
return credentials;
|
|
}
|
|
|
|
void DigestChallenge::fromPj(const pjsip_digest_challenge &prm)
|
|
{
|
|
realm = pj2Str(prm.realm);
|
|
pjsip_param *p = (pjsip_param*)prm.other_param.next;
|
|
while (p != &prm.other_param) {
|
|
otherParam[pj2Str(p->name)] = pj2Str(p->value);
|
|
p = p->next;
|
|
}
|
|
domain = pj2Str(prm.domain);
|
|
nonce = pj2Str(prm.nonce);
|
|
opaque = pj2Str(prm.opaque);
|
|
stale = prm.stale;
|
|
algorithm = pj2Str(prm.algorithm);
|
|
qop = pj2Str(prm.qop);
|
|
}
|
|
|
|
pjsip_digest_challenge DigestChallenge::toPj() const
|
|
{
|
|
pjsip_digest_challenge challenge;
|
|
pj_list_init(&challenge.other_param);
|
|
challenge.realm = str2Pj(realm);
|
|
challenge.domain = str2Pj(domain);
|
|
for (std::map<std::string, std::string>::const_iterator it = otherParam.begin();
|
|
it != otherParam.end(); ++it) {
|
|
pjsip_param other_param;
|
|
other_param.name = str2Pj(it->first);
|
|
other_param.value = str2Pj(it->second);
|
|
pj_list_push_back(&challenge.other_param, &other_param);
|
|
}
|
|
challenge.nonce = str2Pj(nonce);
|
|
challenge.opaque = str2Pj(opaque);
|
|
challenge.stale = stale;
|
|
challenge.algorithm = str2Pj(algorithm);
|
|
challenge.qop = str2Pj(qop);
|
|
return challenge;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
IpChangeParam::IpChangeParam()
|
|
{
|
|
pjsua_ip_change_param param;
|
|
pjsua_ip_change_param_default(¶m);
|
|
fromPj(param);
|
|
}
|
|
|
|
|
|
pjsua_ip_change_param IpChangeParam::toPj() const
|
|
{
|
|
pjsua_ip_change_param param;
|
|
pjsua_ip_change_param_default(¶m);
|
|
|
|
param.restart_listener = restartListener;
|
|
param.restart_lis_delay = restartLisDelay;
|
|
|
|
return param;
|
|
}
|
|
|
|
|
|
void IpChangeParam::fromPj(const pjsua_ip_change_param ¶m)
|
|
{
|
|
restartListener = PJ2BOOL(param.restart_listener);
|
|
restartLisDelay = param.restart_lis_delay;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
UaConfig::UaConfig()
|
|
: mainThreadOnly(false)
|
|
{
|
|
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]));
|
|
}
|
|
for (i=0; i<ua_cfg.outbound_proxy_cnt; ++i) {
|
|
this->outboundProxies.push_back(pj2Str(ua_cfg.outbound_proxy[i]));
|
|
}
|
|
|
|
this->stunTryIpv6 = PJ2BOOL(ua_cfg.stun_try_ipv6);
|
|
this->stunIgnoreFailure = PJ2BOOL(ua_cfg.stun_ignore_failure);
|
|
this->natTypeInSdp = ua_cfg.nat_type_in_sdp;
|
|
this->mwiUnsolicitedEnabled = PJ2BOOL(ua_cfg.enable_unsolicited_mwi);
|
|
this->enableUpnp = PJ2BOOL(ua_cfg.enable_upnp);
|
|
this->upnpIfName = pj2Str(ua_cfg.upnp_if_name);
|
|
}
|
|
|
|
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;
|
|
|
|
for (i=0; i<this->outboundProxies.size() &&
|
|
i<PJ_ARRAY_SIZE(pua_cfg.outbound_proxy); ++i)
|
|
{
|
|
pua_cfg.outbound_proxy[i] = str2Pj(this->outboundProxies[i]);
|
|
}
|
|
pua_cfg.outbound_proxy_cnt= i;
|
|
|
|
pua_cfg.nat_type_in_sdp = this->natTypeInSdp;
|
|
pua_cfg.enable_unsolicited_mwi = this->mwiUnsolicitedEnabled;
|
|
pua_cfg.stun_try_ipv6 = this->stunTryIpv6;
|
|
pua_cfg.stun_ignore_failure = this->stunIgnoreFailure;
|
|
pua_cfg.enable_upnp = this->enableUpnp;
|
|
pua_cfg.upnp_if_name = str2Pj(this->upnpIfName);
|
|
|
|
return pua_cfg;
|
|
}
|
|
|
|
void UaConfig::readObject(const ContainerNode &node) PJSUA2_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, stunTryIpv6);
|
|
NODE_READ_BOOL ( this_node, stunIgnoreFailure);
|
|
NODE_READ_INT ( this_node, natTypeInSdp);
|
|
NODE_READ_BOOL ( this_node, mwiUnsolicitedEnabled);
|
|
NODE_READ_BOOL ( this_node, enableUpnp);
|
|
NODE_READ_STRING ( this_node, upnpIfName);
|
|
}
|
|
|
|
void UaConfig::writeObject(ContainerNode &node) const PJSUA2_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, stunTryIpv6);
|
|
NODE_WRITE_BOOL ( this_node, stunIgnoreFailure);
|
|
NODE_WRITE_INT ( this_node, natTypeInSdp);
|
|
NODE_WRITE_BOOL ( this_node, mwiUnsolicitedEnabled);
|
|
NODE_WRITE_BOOL ( this_node, enableUpnp);
|
|
NODE_WRITE_STRING ( this_node, upnpIfName);
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
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) PJSUA2_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 PJSUA2_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->jbDiscardAlgo = mc.jb_discard_algo;
|
|
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.jb_discard_algo = this->jbDiscardAlgo;
|
|
mcfg.snd_auto_close_time = this->sndAutoCloseTime;
|
|
mcfg.vid_preview_enable_native = this->vidPreviewEnableNative;
|
|
|
|
return mcfg;
|
|
}
|
|
|
|
void MediaConfig::readObject(const ContainerNode &node) PJSUA2_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_NUM_T ( this_node, pjmedia_jb_discard_algo, jbDiscardAlgo);
|
|
NODE_READ_INT ( this_node, sndAutoCloseTime);
|
|
NODE_READ_BOOL ( this_node, vidPreviewEnableNative);
|
|
}
|
|
|
|
void MediaConfig::writeObject(ContainerNode &node) const PJSUA2_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_NUM_T ( this_node, pjmedia_jb_discard_algo, jbDiscardAlgo);
|
|
NODE_WRITE_INT ( this_node, sndAutoCloseTime);
|
|
NODE_WRITE_BOOL ( this_node, vidPreviewEnableNative);
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
void EpConfig::readObject(const ContainerNode &node) PJSUA2_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 PJSUA2_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():
|
|
#if !DEPRECATED_FOR_TICKET_2232
|
|
mediaListMutex(NULL),
|
|
#endif
|
|
writer(NULL), threadDescMutex(NULL), mainThreadOnly(false),
|
|
mainThread(NULL), pendingJobSize(0)
|
|
{
|
|
if (instance_) {
|
|
PJSUA2_RAISE_ERROR(PJ_EEXISTS);
|
|
}
|
|
|
|
audioDevMgr = new AudDevManager();
|
|
videoDevMgr = new VidDevManager();
|
|
|
|
instance_ = this;
|
|
}
|
|
|
|
Endpoint& Endpoint::instance() PJSUA2_THROW(Error)
|
|
{
|
|
if (!instance_) {
|
|
PJSUA2_RAISE_ERROR(PJ_ENOTFOUND);
|
|
}
|
|
return *instance_;
|
|
}
|
|
|
|
Endpoint::~Endpoint()
|
|
{
|
|
while (!pendingJobs.empty()) {
|
|
delete pendingJobs.front();
|
|
pendingJobs.pop_front();
|
|
}
|
|
|
|
#if !DEPRECATED_FOR_TICKET_2232
|
|
clearCodecInfoList(codecInfoList);
|
|
clearCodecInfoList(videoCodecInfoList);
|
|
#endif
|
|
|
|
delete audioDevMgr;
|
|
delete videoDevMgr;
|
|
|
|
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 (!writer) return;
|
|
|
|
if (mainThreadOnly && pj_thread_this() != mainThread) {
|
|
PendingLog *job = new PendingLog;
|
|
job->entry = entry;
|
|
utilAddPendingJob(job);
|
|
} else {
|
|
writer->write(entry);
|
|
}
|
|
}
|
|
|
|
pj_status_t Endpoint::onCredAuth(OnCredAuthParam &prm)
|
|
{
|
|
PJ_UNUSED_ARG(prm);
|
|
return PJ_ENOTSUP;
|
|
}
|
|
|
|
/* 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)(size_t)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;
|
|
|
|
/* Best effort to handle race condition with utilTimerCancel() */
|
|
ut->signature = 0xFFFFFFFE;
|
|
|
|
ep.onTimer(ut->prm);
|
|
delete ut;
|
|
}
|
|
|
|
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.type = tp->type_name;
|
|
prm.state = state;
|
|
prm.lastError = info ? info->status : PJ_SUCCESS;
|
|
|
|
#if defined(PJSIP_HAS_TLS_TRANSPORT) && PJSIP_HAS_TLS_TRANSPORT!=0
|
|
if (!pj_ansi_stricmp(tp->type_name, "tls") && info && info->ext_info &&
|
|
(state == PJSIP_TP_STATE_CONNECTED ||
|
|
((pjsip_tls_state_info*)info->ext_info)->
|
|
ssl_sock_info->verify_status != PJ_SUCCESS))
|
|
{
|
|
prm.tlsInfo.fromPj(*((pjsip_tls_state_info*)info->ext_info));
|
|
}
|
|
#endif
|
|
|
|
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;
|
|
}
|
|
|
|
pjsua_call *call = &pjsua_var.calls[call_id];
|
|
if (!call->incoming_data) {
|
|
/* This happens when the incoming call callback has been called from
|
|
* inside the on_create_media_transport() callback. So we simply
|
|
* return here to avoid calling the callback twice.
|
|
*/
|
|
return;
|
|
}
|
|
|
|
/* call callback */
|
|
OnIncomingCallParam prm;
|
|
prm.callId = call_id;
|
|
prm.rdata.fromPj(*rdata);
|
|
|
|
acc->onIncomingCall(prm);
|
|
|
|
/* Free cloned rdata. */
|
|
pjsip_rx_data_free_cloned(call->incoming_data);
|
|
call->incoming_data = NULL;
|
|
|
|
/* 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;
|
|
|
|
if (mwi_info->evsub) {
|
|
prm.state = pjsip_evsub_get_state(mwi_info->evsub);
|
|
} else {
|
|
/* Unsolicited MWI */
|
|
prm.state = PJSIP_EVSUB_STATE_NULL;
|
|
}
|
|
prm.rdata.fromPj(*mwi_info->rdata);
|
|
|
|
Account *acc = lookupAcc(acc_id, "on_mwi_info()");
|
|
if (!acc) {
|
|
/* Ignored */
|
|
return;
|
|
}
|
|
|
|
acc->onMwiInfo(prm);
|
|
}
|
|
|
|
void Endpoint::on_acc_find_for_incoming(const pjsip_rx_data *rdata,
|
|
pjsua_acc_id* acc_id)
|
|
{
|
|
OnSelectAccountParam prm;
|
|
|
|
pj_assert(rdata && acc_id);
|
|
prm.rdata.fromPj(*((pjsip_rx_data *)rdata));
|
|
prm.accountIndex = *acc_id;
|
|
|
|
instance_->onSelectAccount(prm);
|
|
|
|
*acc_id = prm.accountIndex;
|
|
}
|
|
|
|
void Endpoint::on_buddy_state(pjsua_buddy_id buddy_id)
|
|
{
|
|
Buddy b(buddy_id);
|
|
Buddy *buddy = b.getOriginalInstance();
|
|
if (!buddy || !buddy->isValid()) {
|
|
/* Ignored */
|
|
return;
|
|
}
|
|
|
|
buddy->onBuddyState();
|
|
}
|
|
|
|
void Endpoint::on_buddy_evsub_state(pjsua_buddy_id buddy_id,
|
|
pjsip_evsub *sub,
|
|
pjsip_event *event)
|
|
{
|
|
PJ_UNUSED_ARG(sub);
|
|
|
|
Buddy b(buddy_id);
|
|
Buddy *buddy = b.getOriginalInstance();
|
|
if (!buddy || !buddy->isValid()) {
|
|
/* Ignored */
|
|
return;
|
|
}
|
|
|
|
OnBuddyEvSubStateParam prm;
|
|
prm.e.fromPj(*event);
|
|
|
|
buddy->onBuddyEvSubState(prm);
|
|
}
|
|
|
|
// 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->sdp_pool = pool;
|
|
call->onCallSdpCreated(prm);
|
|
|
|
/* Check if application modifies the SDP */
|
|
if (orig_sdp != prm.sdp.wholeSdp) {
|
|
pjmedia_sdp_session *new_sdp;
|
|
pj_str_t dup_new_sdp;
|
|
pj_str_t new_sdp_str = {(char*)prm.sdp.wholeSdp.c_str(),
|
|
(pj_ssize_t)prm.sdp.wholeSdp.size()};
|
|
pj_status_t status;
|
|
|
|
pj_strdup(pool, &dup_new_sdp, &new_sdp_str);
|
|
status = pjmedia_sdp_parse(pool, dup_new_sdp.ptr,
|
|
dup_new_sdp.slen, &new_sdp);
|
|
if (status != PJ_SUCCESS) {
|
|
PJ_PERROR(4,(THIS_FILE, status,
|
|
"Failed to parse the modified SDP"));
|
|
} else {
|
|
pj_memcpy(sdp, new_sdp, sizeof(*sdp));
|
|
}
|
|
}
|
|
}
|
|
|
|
void Endpoint::on_stream_precreate(pjsua_call_id call_id,
|
|
pjsua_on_stream_precreate_param *param)
|
|
{
|
|
Call *call = Call::lookup(call_id);
|
|
if (!call) {
|
|
return;
|
|
}
|
|
|
|
OnStreamPreCreateParam prm;
|
|
prm.streamIdx = param->stream_idx;
|
|
prm.streamInfo.fromPj(param->stream_info);
|
|
|
|
call->onStreamPreCreate(prm);
|
|
|
|
/* Copy back only the fields which are allowed to be changed. */
|
|
if (param->stream_info.type == PJMEDIA_TYPE_AUDIO) {
|
|
param->stream_info.info.aud.jb_init = prm.streamInfo.jbInit;
|
|
param->stream_info.info.aud.jb_min_pre = prm.streamInfo.jbMinPre;
|
|
param->stream_info.info.aud.jb_max_pre = prm.streamInfo.jbMaxPre;
|
|
param->stream_info.info.aud.jb_max = prm.streamInfo.jbMax;
|
|
param->stream_info.info.aud.jb_discard_algo = prm.streamInfo.jbDiscardAlgo;
|
|
#if defined(PJMEDIA_STREAM_ENABLE_KA) && (PJMEDIA_STREAM_ENABLE_KA != 0)
|
|
param->stream_info.info.aud.use_ka = prm.streamInfo.useKa;
|
|
#endif
|
|
param->stream_info.info.aud.rtcp_sdes_bye_disabled = prm.streamInfo.rtcpSdesByeDisabled;
|
|
} else if (param->stream_info.type == PJMEDIA_TYPE_VIDEO) {
|
|
param->stream_info.info.vid.jb_init = prm.streamInfo.jbInit;
|
|
param->stream_info.info.vid.jb_min_pre = prm.streamInfo.jbMinPre;
|
|
param->stream_info.info.vid.jb_max_pre = prm.streamInfo.jbMaxPre;
|
|
param->stream_info.info.vid.jb_max = prm.streamInfo.jbMax;
|
|
#if defined(PJMEDIA_STREAM_ENABLE_KA) && (PJMEDIA_STREAM_ENABLE_KA != 0)
|
|
param->stream_info.info.vid.use_ka = prm.streamInfo.useKa;
|
|
#endif
|
|
param->stream_info.info.vid.rtcp_sdes_bye_disabled = prm.streamInfo.rtcpSdesByeDisabled;
|
|
param->stream_info.info.vid.codec_param->enc_fmt = prm.streamInfo.vidCodecParam.encFmt.toPj();
|
|
|
|
}
|
|
}
|
|
|
|
void Endpoint::on_stream_created2(pjsua_call_id call_id,
|
|
pjsua_on_stream_created_param *param)
|
|
{
|
|
Call *call = Call::lookup(call_id);
|
|
if (!call) {
|
|
return;
|
|
}
|
|
|
|
OnStreamCreatedParam prm;
|
|
prm.stream = param->stream;
|
|
prm.streamIdx = param->stream_idx;
|
|
prm.destroyPort = (param->destroy_port != PJ_FALSE);
|
|
prm.pPort = (MediaPort)param->port;
|
|
|
|
call->onStreamCreated(prm);
|
|
|
|
param->destroy_port = prm.destroyPort;
|
|
param->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) {
|
|
/* This can happen for call disconnection case. The callback
|
|
* should have been called from on_call_state() instead.
|
|
*/
|
|
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_snprintf(buf, sizeof(buf), "%c", digit);
|
|
job->prm.digit = string(buf);
|
|
|
|
Endpoint::instance().utilAddPendingJob(job);
|
|
}
|
|
|
|
void Endpoint::on_dtmf_digit2(pjsua_call_id call_id,
|
|
const pjsua_dtmf_info *info)
|
|
{
|
|
Call *call = Call::lookup(call_id);
|
|
if (!call) {
|
|
return;
|
|
}
|
|
|
|
PendingOnDtmfDigitCallback *job = new PendingOnDtmfDigitCallback;
|
|
job->call_id = call_id;
|
|
char buf[10];
|
|
pj_ansi_snprintf(buf, sizeof(buf), "%c", info->digit);
|
|
job->prm.digit = string(buf);
|
|
job->prm.method = info->method;
|
|
job->prm.duration = info->duration;
|
|
|
|
Endpoint::instance().utilAddPendingJob(job);
|
|
}
|
|
|
|
struct PendingOnDtmfEventCallback : public PendingJob
|
|
{
|
|
int call_id;
|
|
OnDtmfEventParam prm;
|
|
|
|
virtual void execute(bool is_pending)
|
|
{
|
|
PJ_UNUSED_ARG(is_pending);
|
|
|
|
Call *call = Call::lookup(call_id);
|
|
if (!call)
|
|
return;
|
|
|
|
call->onDtmfEvent(prm);
|
|
|
|
/* If this event indicates a new DTMF digit, invoke onDtmfDigit
|
|
* as well.
|
|
* Note that the duration is pretty much useless in this context as it
|
|
* will most likely equal the ptime of one frame received via RTP in
|
|
* milliseconds. Since the application can't receive updates to the
|
|
* duration via this interface and the total duration of the event is
|
|
* not known yet, just indicate an unknown duration.
|
|
*/
|
|
if (!(prm.flags & PJMEDIA_STREAM_DTMF_IS_UPDATE)) {
|
|
OnDtmfDigitParam prmBasic;
|
|
prmBasic.method = prm.method;
|
|
prmBasic.digit = prm.digit;
|
|
prmBasic.duration = PJSUA_UNKNOWN_DTMF_DURATION;
|
|
call->onDtmfDigit(prmBasic);
|
|
}
|
|
}
|
|
};
|
|
|
|
void Endpoint::on_dtmf_event(pjsua_call_id call_id,
|
|
const pjsua_dtmf_event* event)
|
|
{
|
|
Call *call = Call::lookup(call_id);
|
|
if (!call) {
|
|
return;
|
|
}
|
|
|
|
PendingOnDtmfEventCallback *job = new PendingOnDtmfEventCallback;
|
|
job->call_id = call_id;
|
|
char buf[10];
|
|
pj_ansi_snprintf(buf, sizeof(buf), "%c", event->digit);
|
|
job->prm.method = event->method;
|
|
job->prm.timestamp = event->timestamp;
|
|
job->prm.digit = string(buf);
|
|
job->prm.duration = event->duration;
|
|
job->prm.flags = event->flags;
|
|
|
|
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);
|
|
prm.newCall = NULL;
|
|
|
|
call->onCallTransferRequest(prm);
|
|
|
|
*code = prm.statusCode;
|
|
*opt = prm.opt.toPj();
|
|
if (*code/100 <= 2) {
|
|
if (prm.newCall) {
|
|
/* Sanity checks */
|
|
pj_assert(prm.newCall->id == PJSUA_INVALID_ID);
|
|
pj_assert(prm.newCall->acc.getId() == call->acc.getId());
|
|
|
|
/* We don't manage (e.g: create, delete) the call child,
|
|
* so let's just override any existing child.
|
|
*/
|
|
call->child = prm.newCall;
|
|
call->child->id = PJSUA_INVALID_ID;
|
|
|
|
/* The newCall shares the same user_data as the parent call,
|
|
* the next Call::lookup(new_call_id) will assign the call ID
|
|
* and update user_data for the newCall.
|
|
*/
|
|
} else {
|
|
PJ_LOG(3,(THIS_FILE,
|
|
"Warning: application reuses Call instance in "
|
|
"call transfer (call ID:%d)", call_id));
|
|
}
|
|
}
|
|
}
|
|
|
|
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);
|
|
prm.newCall = NULL;
|
|
|
|
call->onCallReplaceRequest(prm);
|
|
|
|
*st_code = prm.statusCode;
|
|
*st_text = str2Pj(prm.reason);
|
|
*opt = prm.opt.toPj();
|
|
if (prm.newCall && prm.newCall != call) {
|
|
/* Sanity checks */
|
|
pj_assert(prm.newCall->id == PJSUA_INVALID_ID);
|
|
pj_assert(prm.newCall->acc.getId() == call->acc.getId());
|
|
|
|
/* We don't manage (e.g: create, delete) the call child,
|
|
* so let's just override any existing child.
|
|
*/
|
|
call->child = prm.newCall;
|
|
call->child->id = PJSUA_INVALID_ID;
|
|
|
|
/* The newCall shares the same user_data as the parent call,
|
|
* the next Call::lookup(new_call_id) will assign the call ID
|
|
* and update user_data for the newCall.
|
|
*/
|
|
} else {
|
|
PJ_LOG(3,(THIS_FILE,
|
|
"Warning: application has not created new Call instance "
|
|
"for call replace request (call ID:%d)", call_id));
|
|
}
|
|
}
|
|
|
|
void Endpoint::on_call_replaced(pjsua_call_id old_call_id,
|
|
pjsua_call_id new_call_id)
|
|
{
|
|
/* Lookup the new call first, to avoid Call::lookup() overwriting
|
|
* Call.id (to the new Call).
|
|
*/
|
|
Call *new_call = Call::lookup(new_call_id);
|
|
|
|
Call *call = Call::lookup(old_call_id);
|
|
if (!call) {
|
|
return;
|
|
}
|
|
|
|
/* Check if new call object has not been created in
|
|
* onCallReplaceRequest().
|
|
*/
|
|
if (new_call == call)
|
|
new_call = NULL;
|
|
|
|
OnCallReplacedParam prm;
|
|
prm.newCallId = new_call_id;
|
|
prm.newCall = new_call;
|
|
|
|
call->onCallReplaced(prm);
|
|
|
|
if (prm.newCall && prm.newCall != call) {
|
|
/* Sanity checks */
|
|
pj_assert(prm.newCall->id == new_call_id);
|
|
pj_assert(prm.newCall->acc.getId() == call->acc.getId());
|
|
pj_assert(pjsua_call_get_user_data(new_call_id) == prm.newCall);
|
|
|
|
/* Warn if new_call created in onCallReplaceRequest() is changed */
|
|
if (new_call && new_call != prm.newCall) {
|
|
PJ_LOG(3,(THIS_FILE,
|
|
"Warning: application has created a new Call instance "
|
|
"in onCallReplaceRequest, but created another in "
|
|
"onCallReplaced (call ID:%d)",
|
|
new_call_id));
|
|
}
|
|
} else {
|
|
PJ_LOG(3,(THIS_FILE,
|
|
"Warning: application has not created new Call instance "
|
|
"for call replace (old call ID:%d, new call ID:%d)",
|
|
old_call_id, new_call_id));
|
|
}
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
void Endpoint::on_call_rx_reinvite(pjsua_call_id call_id,
|
|
const pjmedia_sdp_session *offer,
|
|
pjsip_rx_data *rdata,
|
|
void *reserved,
|
|
pj_bool_t *async,
|
|
pjsip_status_code *code,
|
|
pjsua_call_setting *opt)
|
|
{
|
|
PJ_UNUSED_ARG(reserved);
|
|
|
|
Call *call = Call::lookup(call_id);
|
|
if (!call) {
|
|
return;
|
|
}
|
|
|
|
OnCallRxReinviteParam prm;
|
|
prm.offer.fromPj(*offer);
|
|
prm.rdata.fromPj(*rdata);
|
|
prm.isAsync = PJ2BOOL(*async);
|
|
prm.statusCode = *code;
|
|
prm.opt.fromPj(*opt);
|
|
|
|
call->onCallRxReinvite(prm);
|
|
|
|
*async = prm.isAsync;
|
|
*code = prm.statusCode;
|
|
*opt = prm.opt.toPj();
|
|
}
|
|
|
|
void Endpoint::on_call_tx_offer(pjsua_call_id call_id,
|
|
void *reserved,
|
|
pjsua_call_setting *opt)
|
|
{
|
|
PJ_UNUSED_ARG(reserved);
|
|
|
|
Call *call = Call::lookup(call_id);
|
|
if (!call) {
|
|
return;
|
|
}
|
|
|
|
OnCallTxOfferParam prm;
|
|
prm.opt.fromPj(*opt);
|
|
|
|
call->onCallTxOffer(prm);
|
|
|
|
*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_strxcpy(uristr, "--URI too long--", sizeof(uristr));
|
|
}
|
|
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)
|
|
{
|
|
if (is_pending) {
|
|
/* Can't do this anymore, pointer is invalid */
|
|
prm.ev.pjMediaEvent = NULL;
|
|
}
|
|
|
|
if (call_id == PJSUA_INVALID_ID) {
|
|
OnMediaEventParam prm2;
|
|
prm2.ev = prm.ev;
|
|
Endpoint::instance().onMediaEvent(prm2);
|
|
} else {
|
|
Call *call = Call::lookup(call_id);
|
|
|
|
if (call)
|
|
call->onCallMediaEvent(prm);
|
|
}
|
|
}
|
|
};
|
|
|
|
void Endpoint::on_media_event(pjmedia_event *event)
|
|
{
|
|
PendingOnMediaEventCallback *job = new PendingOnMediaEventCallback;
|
|
|
|
job->call_id = PJSUA_INVALID_ID;
|
|
job->prm.medIdx = 0;
|
|
job->prm.ev.fromPj(*event);
|
|
|
|
Endpoint::instance().utilAddPendingJob(job);
|
|
}
|
|
|
|
void Endpoint::on_call_media_event(pjsua_call_id call_id,
|
|
unsigned med_idx,
|
|
pjmedia_event *event)
|
|
{
|
|
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) {
|
|
pjsua_call *in_call = &pjsua_var.calls[call_id];
|
|
if (in_call->incoming_data) {
|
|
/* This can happen when there is an incoming call but the
|
|
* on_incoming_call() callback hasn't been called. So we need to
|
|
* call the callback here.
|
|
*/
|
|
on_incoming_call(in_call->acc_id, call_id, in_call->incoming_data);
|
|
|
|
/* New call should already be created by app. */
|
|
call = Call::lookup(call_id);
|
|
if (!call) {
|
|
return base_tp;
|
|
}
|
|
if (in_call->inv->dlg->mod_data[pjsua_var.mod.id] == NULL) {
|
|
/* This will enabled notification for fail events related to
|
|
* the call via on_call_state() and on_call_tsx_state().
|
|
*/
|
|
in_call->inv->dlg->mod_data[pjsua_var.mod.id] = in_call;
|
|
in_call->inv->mod_data[pjsua_var.mod.id] = in_call;
|
|
++pjsua_var.call_cnt;
|
|
}
|
|
} else {
|
|
return base_tp;
|
|
}
|
|
}
|
|
|
|
OnCreateMediaTransportParam prm;
|
|
prm.mediaIdx = media_idx;
|
|
prm.mediaTp = base_tp;
|
|
prm.flags = flags;
|
|
|
|
call->onCreateMediaTransport(prm);
|
|
|
|
return (pjmedia_transport *)prm.mediaTp;
|
|
}
|
|
|
|
void Endpoint::on_create_media_transport_srtp(pjsua_call_id call_id,
|
|
unsigned media_idx,
|
|
pjmedia_srtp_setting *srtp_opt)
|
|
{
|
|
Call *call = Call::lookup(call_id);
|
|
if (!call) {
|
|
pjsua_call *in_call = &pjsua_var.calls[call_id];
|
|
if (in_call->incoming_data) {
|
|
/* This can happen when there is an incoming call but the
|
|
* on_incoming_call() callback hasn't been called. So we need to
|
|
* call the callback here.
|
|
*/
|
|
on_incoming_call(in_call->acc_id, call_id, in_call->incoming_data);
|
|
|
|
/* New call should already be created by app. */
|
|
call = Call::lookup(call_id);
|
|
if (!call) {
|
|
return;
|
|
}
|
|
} else {
|
|
return;
|
|
}
|
|
}
|
|
|
|
OnCreateMediaTransportSrtpParam prm;
|
|
prm.mediaIdx = media_idx;
|
|
prm.srtpUse = srtp_opt->use;
|
|
for (unsigned i = 0; i < srtp_opt->crypto_count; i++) {
|
|
SrtpCrypto crypto;
|
|
|
|
crypto.key = pj2Str(srtp_opt->crypto[i].key);
|
|
crypto.name = pj2Str(srtp_opt->crypto[i].name);
|
|
crypto.flags = srtp_opt->crypto[i].flags;
|
|
prm.cryptos.push_back(crypto);
|
|
}
|
|
|
|
call->onCreateMediaTransportSrtp(prm);
|
|
|
|
srtp_opt->use = prm.srtpUse;
|
|
srtp_opt->crypto_count = (unsigned)prm.cryptos.size();
|
|
for (unsigned i = 0; i < srtp_opt->crypto_count; i++) {
|
|
srtp_opt->crypto[i].key = str2Pj(prm.cryptos[i].key);
|
|
srtp_opt->crypto[i].name = str2Pj(prm.cryptos[i].name);
|
|
srtp_opt->crypto[i].flags = prm.cryptos[i].flags;
|
|
}
|
|
}
|
|
|
|
void Endpoint::on_ip_change_progress(pjsua_ip_change_op op,
|
|
pj_status_t status,
|
|
const pjsua_ip_change_op_info *info)
|
|
{
|
|
Endpoint &ep = Endpoint::instance();
|
|
OnIpChangeProgressParam param;
|
|
|
|
param.op = op;
|
|
param.status = status;
|
|
switch (op) {
|
|
case PJSUA_IP_CHANGE_OP_RESTART_LIS:
|
|
param.transportId = info->lis_restart.transport_id;
|
|
break;
|
|
case PJSUA_IP_CHANGE_OP_ACC_SHUTDOWN_TP:
|
|
param.accId = info->acc_shutdown_tp.acc_id;
|
|
break;
|
|
case PJSUA_IP_CHANGE_OP_ACC_UPDATE_CONTACT:
|
|
param.accId = info->acc_update_contact.acc_id;
|
|
param.regInfo.code = info->acc_update_contact.code;
|
|
param.regInfo.isRegister =
|
|
PJ2BOOL(info->acc_update_contact.is_register);
|
|
break;
|
|
case PJSUA_IP_CHANGE_OP_ACC_HANGUP_CALLS:
|
|
param.accId = info->acc_hangup_calls.acc_id;
|
|
param.callId = info->acc_hangup_calls.call_id;
|
|
break;
|
|
case PJSUA_IP_CHANGE_OP_ACC_REINVITE_CALLS:
|
|
param.accId = info->acc_reinvite_calls.acc_id;
|
|
param.callId = info->acc_reinvite_calls.call_id;
|
|
break;
|
|
default:
|
|
param.accId = PJSUA_INVALID_ID;
|
|
break;
|
|
}
|
|
ep.onIpChangeProgress(param);
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
/*
|
|
* 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() PJSUA2_THROW(Error)
|
|
{
|
|
PJSUA2_CHECK_EXPR( pjsua_create() );
|
|
mainThread = pj_thread_this();
|
|
|
|
/* Register library main thread */
|
|
threadDescMap[pj_thread_this()] = NULL;
|
|
}
|
|
|
|
pjsua_state Endpoint::libGetState() const
|
|
{
|
|
return pjsua_get_state();
|
|
}
|
|
|
|
void Endpoint::libInit(const EpConfig &prmEpConfig) PJSUA2_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;
|
|
ua_cfg.cb.on_buddy_evsub_state = &Endpoint::on_buddy_evsub_state;
|
|
ua_cfg.cb.on_acc_find_for_incoming = &Endpoint::on_acc_find_for_incoming;
|
|
ua_cfg.cb.on_ip_change_progress = &Endpoint::on_ip_change_progress;
|
|
|
|
/* 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_precreate = &Endpoint::on_stream_precreate;
|
|
ua_cfg.cb.on_stream_created2 = &Endpoint::on_stream_created2;
|
|
ua_cfg.cb.on_stream_destroyed = &Endpoint::on_stream_destroyed;
|
|
//ua_cfg.cb.on_dtmf_digit = &Endpoint::on_dtmf_digit;
|
|
//ua_cfg.cb.on_dtmf_digit2 = &Endpoint::on_dtmf_digit2;
|
|
ua_cfg.cb.on_dtmf_event = &Endpoint::on_dtmf_event;
|
|
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_rx_reinvite = &Endpoint::on_call_rx_reinvite;
|
|
ua_cfg.cb.on_call_tx_offer = &Endpoint::on_call_tx_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_media_event = &Endpoint::on_media_event;
|
|
ua_cfg.cb.on_call_media_event = &Endpoint::on_call_media_event;
|
|
ua_cfg.cb.on_create_media_transport = &Endpoint::on_create_media_transport;
|
|
ua_cfg.cb.on_stun_resolution_complete =
|
|
&Endpoint::stun_resolve_cb;
|
|
ua_cfg.cb.on_rejected_incoming_call = &Endpoint::on_rejected_incoming_call;
|
|
|
|
/* Init! */
|
|
PJSUA2_CHECK_EXPR( pjsua_init(&ua_cfg, &log_cfg, &med_cfg) );
|
|
|
|
/* Register worker threads */
|
|
int i = pjsua_var.ua_cfg.thread_cnt;
|
|
while (i) {
|
|
pj_thread_t *t = pjsua_var.thread[--i];
|
|
if (t)
|
|
threadDescMap[t] = NULL;
|
|
}
|
|
|
|
/* Register media endpoint worker thread */
|
|
pjmedia_endpt *medept = pjsua_get_pjmedia_endpt();
|
|
i = pjmedia_endpt_get_thread_count(medept);
|
|
while (i) {
|
|
pj_thread_t *t = pjmedia_endpt_get_thread(medept, --i);
|
|
if (t)
|
|
threadDescMap[t] = NULL;
|
|
}
|
|
|
|
PJSUA2_CHECK_EXPR( pj_mutex_create_simple(pjsua_var.pool, "threadDesc",
|
|
&threadDescMutex) );
|
|
|
|
#if !DEPRECATED_FOR_TICKET_2232
|
|
PJSUA2_CHECK_EXPR( pj_mutex_create_recursive(pjsua_var.pool, "mediaList",
|
|
&mediaListMutex) );
|
|
#endif
|
|
}
|
|
|
|
void Endpoint::libStart() PJSUA2_THROW(Error)
|
|
{
|
|
PJSUA2_CHECK_EXPR(pjsua_start());
|
|
}
|
|
|
|
void Endpoint::libRegisterThread(const string &name) PJSUA2_THROW(Error)
|
|
{
|
|
pj_thread_t *thread;
|
|
pj_thread_desc *desc;
|
|
pj_status_t status;
|
|
|
|
desc = (pj_thread_desc*)malloc(sizeof(pj_thread_desc));
|
|
if (!desc) {
|
|
PJSUA2_RAISE_ERROR(PJ_ENOMEM);
|
|
}
|
|
|
|
pj_bzero(desc, sizeof(pj_thread_desc));
|
|
|
|
status = pj_thread_register(name.c_str(), *desc, &thread);
|
|
if (status == PJ_SUCCESS) {
|
|
pj_mutex_lock(threadDescMutex);
|
|
threadDescMap[thread] = desc;
|
|
pj_mutex_unlock(threadDescMutex);
|
|
} else {
|
|
free(desc);
|
|
PJSUA2_RAISE_ERROR(status);
|
|
}
|
|
}
|
|
|
|
bool Endpoint::libIsThreadRegistered()
|
|
{
|
|
if (pj_thread_is_registered()) {
|
|
bool found;
|
|
|
|
pj_mutex_lock(threadDescMutex);
|
|
/* Recheck again if it exists in the thread description map */
|
|
found = (threadDescMap.find(pj_thread_this()) != threadDescMap.end());
|
|
pj_mutex_unlock(threadDescMutex);
|
|
|
|
return found;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
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) PJSUA2_THROW(Error)
|
|
{
|
|
pj_status_t status;
|
|
|
|
if (threadDescMutex) {
|
|
pj_mutex_destroy(threadDescMutex);
|
|
threadDescMutex = NULL;
|
|
}
|
|
|
|
#if !DEPRECATED_FOR_TICKET_2232
|
|
while(mediaList.size() > 0) {
|
|
AudioMedia *cur_media = mediaList[0];
|
|
delete cur_media; /* this will remove itself from the list */
|
|
}
|
|
|
|
if (mediaListMutex) {
|
|
pj_mutex_destroy(mediaListMutex);
|
|
mediaListMutex = NULL;
|
|
}
|
|
#endif
|
|
|
|
status = pjsua_destroy2(flags);
|
|
|
|
delete this->writer;
|
|
this->writer = NULL;
|
|
|
|
#if PJ_LOG_MAX_LEVEL >= 1
|
|
if (pj_log_get_log_func() == &Endpoint::logFunc) {
|
|
pj_log_set_log_func(NULL);
|
|
}
|
|
#endif
|
|
|
|
/* Clean up thread descriptors */
|
|
std::map<pj_thread_t*, pj_thread_desc*>::iterator i;
|
|
for (i = threadDescMap.begin(); i != threadDescMap.end(); ++i) {
|
|
pj_thread_desc* d = (*i).second;
|
|
if (d != NULL)
|
|
free(d);
|
|
}
|
|
threadDescMap.clear();
|
|
|
|
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, ...)
|
|
{
|
|
#if PJ_LOG_MAX_LEVEL >= 1
|
|
va_list arg;
|
|
va_start(arg, format);
|
|
pj_log(sender, level, format, arg );
|
|
va_end(arg);
|
|
#endif
|
|
}
|
|
|
|
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) PJSUA2_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() PJSUA2_THROW(Error)
|
|
{
|
|
#if PJ_HAS_SSL_SOCK
|
|
pj_ssl_cipher ciphers[PJ_SSL_SOCK_MAX_CIPHERS];
|
|
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) PJSUA2_THROW(Error)
|
|
{
|
|
PJSUA2_CHECK_EXPR( pjsua_detect_nat_type() );
|
|
}
|
|
|
|
pj_stun_nat_type Endpoint::natGetType() PJSUA2_THROW(Error)
|
|
{
|
|
pj_stun_nat_type type;
|
|
|
|
PJSUA2_CHECK_EXPR( pjsua_get_nat_type(&type) );
|
|
|
|
return type;
|
|
}
|
|
|
|
void Endpoint::natUpdateStunServers(const StringVector &servers,
|
|
bool wait) PJSUA2_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_update_stun_servers(count, srv, wait) );
|
|
}
|
|
|
|
void Endpoint::natCheckStunServers(const StringVector &servers,
|
|
bool wait,
|
|
Token token) PJSUA2_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) PJSUA2_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)
|
|
PJSUA2_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() const PJSUA2_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) const PJSUA2_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)
|
|
PJSUA2_THROW(Error)
|
|
{
|
|
PJSUA2_CHECK_EXPR( pjsua_transport_set_enable(id, enabled) );
|
|
}
|
|
|
|
void Endpoint::transportClose(TransportId id) PJSUA2_THROW(Error)
|
|
{
|
|
PJSUA2_CHECK_EXPR( pjsua_transport_close(id, PJ_FALSE) );
|
|
}
|
|
|
|
void Endpoint::transportShutdown(TransportHandle tp) PJSUA2_THROW(Error)
|
|
{
|
|
PJSUA2_CHECK_EXPR( pjsip_transport_shutdown((pjsip_transport *)tp) );
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
/*
|
|
* 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();
|
|
}
|
|
|
|
#if !DEPRECATED_FOR_TICKET_2232
|
|
const AudioMediaVector &Endpoint::mediaEnumPorts() const PJSUA2_THROW(Error)
|
|
{
|
|
return mediaList;
|
|
}
|
|
#endif
|
|
|
|
AudioMediaVector2 Endpoint::mediaEnumPorts2() const PJSUA2_THROW(Error)
|
|
{
|
|
AudioMediaVector2 amv2;
|
|
pjsua_conf_port_id ids[PJSUA_MAX_CONF_PORTS];
|
|
unsigned i, count = PJSUA_MAX_CONF_PORTS;
|
|
|
|
PJSUA2_CHECK_EXPR( pjsua_enum_conf_ports(ids, &count) );
|
|
for (i = 0; i < count; ++i) {
|
|
AudioMediaHelper am;
|
|
am.setPortId(ids[i]);
|
|
amv2.push_back(am);
|
|
}
|
|
|
|
return amv2;
|
|
}
|
|
|
|
VideoMediaVector Endpoint::mediaEnumVidPorts() const PJSUA2_THROW(Error)
|
|
{
|
|
#if PJSUA_HAS_VIDEO
|
|
VideoMediaVector vmv;
|
|
pjsua_conf_port_id ids[PJSUA_MAX_CONF_PORTS];
|
|
unsigned i, count = PJSUA_MAX_CONF_PORTS;
|
|
|
|
PJSUA2_CHECK_EXPR( pjsua_vid_conf_enum_ports(ids, &count) );
|
|
for (i = 0; i < count; ++i) {
|
|
VideoMediaHelper vm;
|
|
vm.setPortId(ids[i]);
|
|
vmv.push_back(vm);
|
|
}
|
|
|
|
return vmv;
|
|
#else
|
|
PJSUA2_RAISE_ERROR(PJ_EINVALIDOP);
|
|
#endif
|
|
}
|
|
|
|
void Endpoint::mediaAdd(AudioMedia &media)
|
|
{
|
|
#if !DEPRECATED_FOR_TICKET_2232
|
|
/* mediaList serves mediaEnumPorts() only, once mediaEnumPorts()
|
|
* is removed, this function implementation should be no-op.
|
|
*/
|
|
pj_mutex_lock(mediaListMutex);
|
|
|
|
AudioMediaVector::iterator it = std::find(mediaList.begin(),
|
|
mediaList.end(),
|
|
&media);
|
|
|
|
if (it == mediaList.end())
|
|
mediaList.push_back(&media);
|
|
pj_mutex_unlock(mediaListMutex);
|
|
#else
|
|
PJ_UNUSED_ARG(media);
|
|
#endif
|
|
}
|
|
|
|
void Endpoint::mediaRemove(AudioMedia &media)
|
|
{
|
|
#if !DEPRECATED_FOR_TICKET_2232
|
|
/* mediaList serves mediaEnumPorts() only, once mediaEnumPorts()
|
|
* is removed, this function implementation should be no-op.
|
|
*/
|
|
pj_mutex_lock(mediaListMutex);
|
|
AudioMediaVector::iterator it = std::find(mediaList.begin(),
|
|
mediaList.end(),
|
|
&media);
|
|
|
|
if (it != mediaList.end())
|
|
mediaList.erase(it);
|
|
pj_mutex_unlock(mediaListMutex);
|
|
#else
|
|
PJ_UNUSED_ARG(media);
|
|
#endif
|
|
}
|
|
|
|
bool Endpoint::mediaExists(const AudioMedia &media) const
|
|
{
|
|
pjsua_conf_port_id id = media.getPortId();
|
|
if (id == PJSUA_INVALID_ID || id >= (int)mediaMaxPorts())
|
|
return false;
|
|
|
|
pjsua_conf_port_info pi;
|
|
return (pjsua_conf_get_port_info(id, &pi) == PJ_SUCCESS);
|
|
}
|
|
|
|
AudDevManager &Endpoint::audDevManager()
|
|
{
|
|
return *audioDevMgr;
|
|
}
|
|
|
|
VidDevManager &Endpoint::vidDevManager()
|
|
{
|
|
return *videoDevMgr;
|
|
}
|
|
|
|
/*
|
|
* Codec operations.
|
|
*/
|
|
|
|
#if !DEPRECATED_FOR_TICKET_2232
|
|
const CodecInfoVector &Endpoint::codecEnum() PJSUA2_THROW(Error)
|
|
{
|
|
pjsua_codec_info pj_codec[MAX_CODEC_NUM];
|
|
unsigned count = MAX_CODEC_NUM;
|
|
|
|
PJSUA2_CHECK_EXPR( pjsua_enum_codecs(pj_codec, &count) );
|
|
|
|
updateCodecInfoList(pj_codec, count, codecInfoList);
|
|
return codecInfoList;
|
|
}
|
|
#endif
|
|
|
|
CodecInfoVector2 Endpoint::codecEnum2() const PJSUA2_THROW(Error)
|
|
{
|
|
CodecInfoVector2 civ2;
|
|
pjsua_codec_info pj_codec[MAX_CODEC_NUM];
|
|
unsigned count = MAX_CODEC_NUM;
|
|
|
|
PJSUA2_CHECK_EXPR( pjsua_enum_codecs(pj_codec, &count) );
|
|
for (unsigned i = 0; i<count; ++i) {
|
|
CodecInfo codec_info;
|
|
codec_info.fromPj(pj_codec[i]);
|
|
civ2.push_back(codec_info);
|
|
}
|
|
return civ2;
|
|
}
|
|
|
|
void Endpoint::codecSetPriority(const string &codec_id,
|
|
pj_uint8_t priority) PJSUA2_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
|
|
PJSUA2_THROW(Error)
|
|
{
|
|
CodecParam param;
|
|
pjmedia_codec_param pj_param;
|
|
pj_str_t codec_str = str2Pj(codec_id);
|
|
|
|
PJSUA2_CHECK_EXPR( pjsua_codec_get_param(&codec_str, &pj_param) );
|
|
|
|
param.fromPj(pj_param);
|
|
return param;
|
|
}
|
|
|
|
void Endpoint::codecSetParam(const string &codec_id,
|
|
const CodecParam param) PJSUA2_THROW(Error)
|
|
{
|
|
pj_str_t codec_str = str2Pj(codec_id);
|
|
pjmedia_codec_param pj_param = param.toPj();
|
|
|
|
PJSUA2_CHECK_EXPR( pjsua_codec_set_param(&codec_str, &pj_param) );
|
|
}
|
|
|
|
#if defined(PJMEDIA_HAS_OPUS_CODEC) && (PJMEDIA_HAS_OPUS_CODEC!=0)
|
|
|
|
CodecOpusConfig Endpoint::getCodecOpusConfig() const PJSUA2_THROW(Error)
|
|
{
|
|
pjmedia_codec_opus_config opus_cfg;
|
|
CodecOpusConfig config;
|
|
|
|
PJSUA2_CHECK_EXPR(pjmedia_codec_opus_get_config(&opus_cfg));
|
|
config.fromPj(opus_cfg);
|
|
|
|
return config;
|
|
}
|
|
|
|
void Endpoint::setCodecOpusConfig(const CodecOpusConfig &opus_cfg)
|
|
PJSUA2_THROW(Error)
|
|
{
|
|
const pj_str_t codec_id = {(char *)"opus", 4};
|
|
pjmedia_codec_param param;
|
|
pjmedia_codec_opus_config new_opus_cfg;
|
|
|
|
PJSUA2_CHECK_EXPR(pjsua_codec_get_param(&codec_id, ¶m));
|
|
new_opus_cfg = opus_cfg.toPj();
|
|
|
|
PJSUA2_CHECK_EXPR(pjmedia_codec_opus_set_default_param(&new_opus_cfg,
|
|
¶m));
|
|
}
|
|
|
|
#endif
|
|
|
|
void Endpoint::clearCodecInfoList(CodecInfoVector &codec_list)
|
|
{
|
|
for (unsigned i=0;i<codec_list.size();++i) {
|
|
delete codec_list[i];
|
|
}
|
|
codec_list.clear();
|
|
}
|
|
|
|
void Endpoint::updateCodecInfoList(pjsua_codec_info pj_codec[],
|
|
unsigned count,
|
|
CodecInfoVector &codec_list)
|
|
{
|
|
pj_enter_critical_section();
|
|
clearCodecInfoList(codec_list);
|
|
for (unsigned i = 0; i<count; ++i) {
|
|
CodecInfo *codec_info = new CodecInfo;
|
|
|
|
codec_info->fromPj(pj_codec[i]);
|
|
codec_list.push_back(codec_info);
|
|
}
|
|
pj_leave_critical_section();
|
|
}
|
|
|
|
#if !DEPRECATED_FOR_TICKET_2232
|
|
const CodecInfoVector &Endpoint::videoCodecEnum() PJSUA2_THROW(Error)
|
|
{
|
|
#if PJSUA_HAS_VIDEO
|
|
pjsua_codec_info pj_codec[MAX_CODEC_NUM];
|
|
unsigned count = MAX_CODEC_NUM;
|
|
|
|
PJSUA2_CHECK_EXPR(pjsua_vid_enum_codecs(pj_codec, &count));
|
|
|
|
updateCodecInfoList(pj_codec, count, videoCodecInfoList);
|
|
#endif
|
|
return videoCodecInfoList;
|
|
}
|
|
#endif
|
|
|
|
CodecInfoVector2 Endpoint::videoCodecEnum2() const PJSUA2_THROW(Error)
|
|
{
|
|
CodecInfoVector2 civ2;
|
|
#if PJSUA_HAS_VIDEO
|
|
pjsua_codec_info pj_codec[MAX_CODEC_NUM];
|
|
unsigned count = MAX_CODEC_NUM;
|
|
|
|
PJSUA2_CHECK_EXPR(pjsua_vid_enum_codecs(pj_codec, &count));
|
|
for (unsigned i = 0; i<count; ++i) {
|
|
CodecInfo codec_info;
|
|
codec_info.fromPj(pj_codec[i]);
|
|
civ2.push_back(codec_info);
|
|
}
|
|
#endif
|
|
return civ2;
|
|
}
|
|
|
|
void Endpoint::videoCodecSetPriority(const string &codec_id,
|
|
pj_uint8_t priority) PJSUA2_THROW(Error)
|
|
{
|
|
#if PJSUA_HAS_VIDEO
|
|
pj_str_t codec_str = str2Pj(codec_id);
|
|
PJSUA2_CHECK_EXPR(pjsua_vid_codec_set_priority(&codec_str, priority));
|
|
#else
|
|
PJ_UNUSED_ARG(codec_id);
|
|
PJ_UNUSED_ARG(priority);
|
|
#endif
|
|
}
|
|
|
|
VidCodecParam Endpoint::getVideoCodecParam(const string &codec_id) const
|
|
PJSUA2_THROW(Error)
|
|
{
|
|
VidCodecParam codec_param;
|
|
#if PJSUA_HAS_VIDEO
|
|
pjmedia_vid_codec_param pj_param;
|
|
pj_str_t codec_str = str2Pj(codec_id);
|
|
|
|
PJSUA2_CHECK_EXPR(pjsua_vid_codec_get_param(&codec_str, &pj_param));
|
|
codec_param.fromPj(pj_param);
|
|
#else
|
|
PJ_UNUSED_ARG(codec_id);
|
|
#endif
|
|
return codec_param;
|
|
}
|
|
|
|
void Endpoint::setVideoCodecParam(const string &codec_id,
|
|
const VidCodecParam ¶m)
|
|
PJSUA2_THROW(Error)
|
|
{
|
|
#if PJSUA_HAS_VIDEO
|
|
pj_str_t codec_str = str2Pj(codec_id);
|
|
pjmedia_vid_codec_param pj_param = param.toPj();
|
|
|
|
PJSUA2_CHECK_EXPR(pjsua_vid_codec_set_param(&codec_str, &pj_param));
|
|
#else
|
|
PJ_UNUSED_ARG(codec_id);
|
|
PJ_UNUSED_ARG(param);
|
|
#endif
|
|
}
|
|
|
|
void Endpoint::resetVideoCodecParam(const string &codec_id)
|
|
PJSUA2_THROW(Error)
|
|
{
|
|
#if PJSUA_HAS_VIDEO
|
|
pj_str_t codec_str = str2Pj(codec_id);
|
|
|
|
PJSUA2_CHECK_EXPR(pjsua_vid_codec_set_param(&codec_str, NULL));
|
|
#else
|
|
PJ_UNUSED_ARG(codec_id);
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* Enumerate all SRTP crypto-suite names.
|
|
*/
|
|
StringVector Endpoint::srtpCryptoEnum() PJSUA2_THROW(Error)
|
|
{
|
|
StringVector result;
|
|
#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
|
|
unsigned cnt = PJMEDIA_SRTP_MAX_CRYPTOS;
|
|
pjmedia_srtp_crypto cryptos[PJMEDIA_SRTP_MAX_CRYPTOS];
|
|
|
|
PJSUA2_CHECK_EXPR(pjmedia_srtp_enum_crypto(&cnt, cryptos));
|
|
|
|
for (unsigned i = 0; i < cnt; ++i)
|
|
result.push_back(pj2Str(cryptos[i].name));
|
|
#endif
|
|
|
|
return result;
|
|
}
|
|
|
|
void Endpoint::handleIpChange(const IpChangeParam ¶m) PJSUA2_THROW(Error)
|
|
{
|
|
pjsua_ip_change_param ip_change_param = param.toPj();
|
|
PJSUA2_CHECK_EXPR(pjsua_handle_ip_change(&ip_change_param));
|
|
}
|
|
|
|
pj_status_t Endpoint::on_auth_create_aka_response_callback(pj_pool_t *pool,
|
|
const pjsip_digest_challenge *chal,
|
|
const pjsip_cred_info *cred,
|
|
const pj_str_t *method,
|
|
pjsip_digest_credential *auth)
|
|
{
|
|
OnCredAuthParam prm;
|
|
prm.digestChallenge.fromPj(*chal);
|
|
prm.credentialInfo.fromPj(*cred);
|
|
prm.method = pj2Str(*method);
|
|
prm.digestCredential.fromPj(*auth);
|
|
|
|
pj_status_t status = Endpoint::instance().onCredAuth(prm);
|
|
|
|
if (status == PJ_SUCCESS) {
|
|
pjsip_digest_credential auth_new = prm.digestCredential.toPj();
|
|
// Duplicate in the pool, so that digestCredential
|
|
// is allowed to be destructed at the end of the method.
|
|
pj_strdup(pool, &auth->realm, &auth_new.realm);
|
|
pj_strdup(pool, &auth->username, &auth_new.username);
|
|
pj_strdup(pool, &auth->nonce, &auth_new.nonce);
|
|
pj_strdup(pool, &auth->uri, &auth_new.uri);
|
|
pj_strdup(pool, &auth->response, &auth_new.response);
|
|
pj_strdup(pool, &auth->algorithm, &auth_new.algorithm);
|
|
pj_strdup(pool, &auth->cnonce, &auth_new.cnonce);
|
|
pj_strdup(pool, &auth->opaque, &auth_new.opaque);
|
|
pj_strdup(pool, &auth->qop, &auth_new.qop);
|
|
pj_strdup(pool, &auth->nc, &auth_new.nc);
|
|
pjsip_param_clone(pool, &auth->other_param, &auth_new.other_param);
|
|
}
|
|
#if PJSIP_HAS_DIGEST_AKA_AUTH
|
|
else if (status == PJ_ENOTSUP) {
|
|
status = pjsip_auth_create_aka_response(pool, chal, cred, method, auth);
|
|
}
|
|
#endif
|
|
return status;
|
|
}
|
|
|
|
void Endpoint::on_rejected_incoming_call(
|
|
const pjsua_on_rejected_incoming_call_param *param)
|
|
{
|
|
OnRejectedIncomingCallParam prm;
|
|
prm.callId = param->call_id;
|
|
prm.localInfo = pj2Str(param->local_info);
|
|
prm.remoteInfo = pj2Str(param->remote_info);
|
|
prm.statusCode = param->st_code;
|
|
prm.reason = pj2Str(param->st_text);
|
|
if (param->rdata)
|
|
prm.rdata.fromPj(*param->rdata);
|
|
|
|
Endpoint::instance().onRejectedIncomingCall(prm);
|
|
}
|