diff options
Diffstat (limited to 'pjsip/src/pjsua2')
-rw-r--r-- | pjsip/src/pjsua2/account.cpp | 799 | ||||
-rw-r--r-- | pjsip/src/pjsua2/call.cpp | 719 | ||||
-rw-r--r-- | pjsip/src/pjsua2/endpoint.cpp | 1612 | ||||
-rw-r--r-- | pjsip/src/pjsua2/json.cpp | 544 | ||||
-rw-r--r-- | pjsip/src/pjsua2/media.cpp | 779 | ||||
-rw-r--r-- | pjsip/src/pjsua2/persistent.cpp | 227 | ||||
-rw-r--r-- | pjsip/src/pjsua2/presence.cpp | 190 | ||||
-rw-r--r-- | pjsip/src/pjsua2/siptypes.cpp | 590 | ||||
-rw-r--r-- | pjsip/src/pjsua2/types.cpp | 95 | ||||
-rw-r--r-- | pjsip/src/pjsua2/util.hpp | 45 |
10 files changed, 5600 insertions, 0 deletions
diff --git a/pjsip/src/pjsua2/account.cpp b/pjsip/src/pjsua2/account.cpp new file mode 100644 index 00000000..247c4a4a --- /dev/null +++ b/pjsip/src/pjsua2/account.cpp @@ -0,0 +1,799 @@ +/* $Id$ */ +/* + * 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/account.hpp> +#include <pjsua2/endpoint.hpp> +#include <pjsua2/presence.hpp> +#include <pj/ctype.h> +#include "util.hpp" + +using namespace pj; +using namespace std; + +#define THIS_FILE "account.cpp" + +/////////////////////////////////////////////////////////////////////////////// + +void AccountRegConfig::readObject(const ContainerNode &node) throw(Error) +{ + ContainerNode this_node = node.readContainer("AccountRegConfig"); + + NODE_READ_STRING (this_node, registrarUri); + NODE_READ_BOOL (this_node, registerOnAdd); + NODE_READ_UNSIGNED (this_node, timeoutSec); + NODE_READ_UNSIGNED (this_node, retryIntervalSec); + NODE_READ_UNSIGNED (this_node, firstRetryIntervalSec); + NODE_READ_UNSIGNED (this_node, delayBeforeRefreshSec); + NODE_READ_BOOL (this_node, dropCallsOnFail); + NODE_READ_UNSIGNED (this_node, unregWaitSec); + NODE_READ_UNSIGNED (this_node, proxyUse); + + readSipHeaders(this_node, "headers", headers); +} + +void AccountRegConfig::writeObject(ContainerNode &node) const throw(Error) +{ + ContainerNode this_node = node.writeNewContainer("AccountRegConfig"); + + NODE_WRITE_STRING (this_node, registrarUri); + NODE_WRITE_BOOL (this_node, registerOnAdd); + NODE_WRITE_UNSIGNED (this_node, timeoutSec); + NODE_WRITE_UNSIGNED (this_node, retryIntervalSec); + NODE_WRITE_UNSIGNED (this_node, firstRetryIntervalSec); + NODE_WRITE_UNSIGNED (this_node, delayBeforeRefreshSec); + NODE_WRITE_BOOL (this_node, dropCallsOnFail); + NODE_WRITE_UNSIGNED (this_node, unregWaitSec); + NODE_WRITE_UNSIGNED (this_node, proxyUse); + + writeSipHeaders(this_node, "headers", headers); +} + +/////////////////////////////////////////////////////////////////////////////// + +void AccountSipConfig::readObject(const ContainerNode &node) throw(Error) +{ + ContainerNode this_node = node.readContainer("AccountSipConfig"); + + NODE_READ_STRINGV (this_node, proxies); + NODE_READ_STRING (this_node, contactForced); + NODE_READ_STRING (this_node, contactParams); + NODE_READ_STRING (this_node, contactUriParams); + NODE_READ_BOOL (this_node, authInitialEmpty); + NODE_READ_STRING (this_node, authInitialAlgorithm); + NODE_READ_INT (this_node, transportId); + + ContainerNode creds_node = this_node.readArray("authCreds"); + authCreds.resize(0); + while (creds_node.hasUnread()) { + AuthCredInfo cred; + cred.readObject(creds_node); + authCreds.push_back(cred); + } +} + +void AccountSipConfig::writeObject(ContainerNode &node) const throw(Error) +{ + ContainerNode this_node = node.writeNewContainer("AccountSipConfig"); + + NODE_WRITE_STRINGV (this_node, proxies); + NODE_WRITE_STRING (this_node, contactForced); + NODE_WRITE_STRING (this_node, contactParams); + NODE_WRITE_STRING (this_node, contactUriParams); + NODE_WRITE_BOOL (this_node, authInitialEmpty); + NODE_WRITE_STRING (this_node, authInitialAlgorithm); + NODE_WRITE_INT (this_node, transportId); + + ContainerNode creds_node = this_node.writeNewArray("authCreds"); + for (unsigned i=0; i<authCreds.size(); ++i) { + authCreds[i].writeObject(creds_node); + } +} + +/////////////////////////////////////////////////////////////////////////////// + +void AccountCallConfig::readObject(const ContainerNode &node) throw(Error) +{ + ContainerNode this_node = node.readContainer("AccountCallConfig"); + + NODE_READ_NUM_T ( this_node, pjsua_call_hold_type, holdType); + NODE_READ_NUM_T ( this_node, pjsua_100rel_use, prackUse); + NODE_READ_NUM_T ( this_node, pjsua_sip_timer_use, timerUse); + NODE_READ_UNSIGNED( this_node, timerMinSESec); + NODE_READ_UNSIGNED( this_node, timerSessExpiresSec); +} + +void AccountCallConfig::writeObject(ContainerNode &node) const throw(Error) +{ + ContainerNode this_node = node.writeNewContainer("AccountCallConfig"); + + NODE_WRITE_NUM_T ( this_node, pjsua_call_hold_type, holdType); + NODE_WRITE_NUM_T ( this_node, pjsua_100rel_use, prackUse); + NODE_WRITE_NUM_T ( this_node, pjsua_sip_timer_use, timerUse); + NODE_WRITE_UNSIGNED( this_node, timerMinSESec); + NODE_WRITE_UNSIGNED( this_node, timerSessExpiresSec); +} + +/////////////////////////////////////////////////////////////////////////////// + +void AccountPresConfig::readObject(const ContainerNode &node) throw(Error) +{ + ContainerNode this_node = node.readContainer("AccountPresConfig"); + + NODE_READ_BOOL ( this_node, publishEnabled); + NODE_READ_BOOL ( this_node, publishQueue); + NODE_READ_UNSIGNED( this_node, publishShutdownWaitMsec); + NODE_READ_STRING ( this_node, pidfTupleId); + + readSipHeaders(this_node, "headers", headers); +} + +void AccountPresConfig::writeObject(ContainerNode &node) const throw(Error) +{ + ContainerNode this_node = node.writeNewContainer("AccountPresConfig"); + + NODE_WRITE_BOOL ( this_node, publishEnabled); + NODE_WRITE_BOOL ( this_node, publishQueue); + NODE_WRITE_UNSIGNED( this_node, publishShutdownWaitMsec); + NODE_WRITE_STRING ( this_node, pidfTupleId); + + writeSipHeaders(this_node, "headers", headers); +} + +/////////////////////////////////////////////////////////////////////////////// + +void AccountMwiConfig::readObject(const ContainerNode &node) throw(Error) +{ + ContainerNode this_node = node.readContainer("AccountMwiConfig"); + + NODE_READ_BOOL ( this_node, enabled); + NODE_READ_UNSIGNED( this_node, expirationSec); +} + +void AccountMwiConfig::writeObject(ContainerNode &node) const throw(Error) +{ + ContainerNode this_node = node.writeNewContainer("AccountMwiConfig"); + + NODE_WRITE_BOOL ( this_node, enabled); + NODE_WRITE_UNSIGNED( this_node, expirationSec); +} + +/////////////////////////////////////////////////////////////////////////////// + +void AccountNatConfig::readObject(const ContainerNode &node) throw(Error) +{ + ContainerNode this_node = node.readContainer("AccountNatConfig"); + + NODE_READ_NUM_T ( this_node, pjsua_stun_use, sipStunUse); + NODE_READ_NUM_T ( this_node, pjsua_stun_use, mediaStunUse); + NODE_READ_BOOL ( this_node, iceEnabled); + NODE_READ_INT ( this_node, iceMaxHostCands); + NODE_READ_BOOL ( this_node, iceAggressiveNomination); + NODE_READ_UNSIGNED( this_node, iceNominatedCheckDelayMsec); + NODE_READ_INT ( this_node, iceWaitNominationTimeoutMsec); + NODE_READ_BOOL ( this_node, iceNoRtcp); + NODE_READ_BOOL ( this_node, iceAlwaysUpdate); + NODE_READ_BOOL ( this_node, turnEnabled); + NODE_READ_STRING ( this_node, turnServer); + NODE_READ_NUM_T ( this_node, pj_turn_tp_type, turnConnType); + NODE_READ_STRING ( this_node, turnUserName); + NODE_READ_INT ( this_node, turnPasswordType); + NODE_READ_STRING ( this_node, turnPassword); + NODE_READ_INT ( this_node, contactRewriteUse); + NODE_READ_INT ( this_node, contactRewriteMethod); + NODE_READ_INT ( this_node, viaRewriteUse); + NODE_READ_INT ( this_node, sdpNatRewriteUse); + NODE_READ_INT ( this_node, sipOutboundUse); + NODE_READ_STRING ( this_node, sipOutboundInstanceId); + NODE_READ_STRING ( this_node, sipOutboundRegId); + NODE_READ_UNSIGNED( this_node, udpKaIntervalSec); + NODE_READ_STRING ( this_node, udpKaData); +} + +void AccountNatConfig::writeObject(ContainerNode &node) const throw(Error) +{ + ContainerNode this_node = node.writeNewContainer("AccountNatConfig"); + + NODE_WRITE_NUM_T ( this_node, pjsua_stun_use, sipStunUse); + NODE_WRITE_NUM_T ( this_node, pjsua_stun_use, mediaStunUse); + NODE_WRITE_BOOL ( this_node, iceEnabled); + NODE_WRITE_INT ( this_node, iceMaxHostCands); + NODE_WRITE_BOOL ( this_node, iceAggressiveNomination); + NODE_WRITE_UNSIGNED( this_node, iceNominatedCheckDelayMsec); + NODE_WRITE_INT ( this_node, iceWaitNominationTimeoutMsec); + NODE_WRITE_BOOL ( this_node, iceNoRtcp); + NODE_WRITE_BOOL ( this_node, iceAlwaysUpdate); + NODE_WRITE_BOOL ( this_node, turnEnabled); + NODE_WRITE_STRING ( this_node, turnServer); + NODE_WRITE_NUM_T ( this_node, pj_turn_tp_type, turnConnType); + NODE_WRITE_STRING ( this_node, turnUserName); + NODE_WRITE_INT ( this_node, turnPasswordType); + NODE_WRITE_STRING ( this_node, turnPassword); + NODE_WRITE_INT ( this_node, contactRewriteUse); + NODE_WRITE_INT ( this_node, contactRewriteMethod); + NODE_WRITE_INT ( this_node, viaRewriteUse); + NODE_WRITE_INT ( this_node, sdpNatRewriteUse); + NODE_WRITE_INT ( this_node, sipOutboundUse); + NODE_WRITE_STRING ( this_node, sipOutboundInstanceId); + NODE_WRITE_STRING ( this_node, sipOutboundRegId); + NODE_WRITE_UNSIGNED( this_node, udpKaIntervalSec); + NODE_WRITE_STRING ( this_node, udpKaData); +} + +/////////////////////////////////////////////////////////////////////////////// + +void AccountMediaConfig::readObject(const ContainerNode &node) throw(Error) +{ + ContainerNode this_node = node.readContainer("AccountMediaConfig"); + + NODE_READ_BOOL ( this_node, lockCodecEnabled); + NODE_READ_BOOL ( this_node, streamKaEnabled); + NODE_READ_NUM_T ( this_node, pjmedia_srtp_use, srtpUse); + NODE_READ_INT ( this_node, srtpSecureSignaling); + NODE_READ_NUM_T ( this_node, pjsua_ipv6_use, ipv6Use); + NODE_READ_OBJ ( this_node, transportConfig); +} + +void AccountMediaConfig::writeObject(ContainerNode &node) const throw(Error) +{ + ContainerNode this_node = node.writeNewContainer("AccountMediaConfig"); + + NODE_WRITE_BOOL ( this_node, lockCodecEnabled); + NODE_WRITE_BOOL ( this_node, streamKaEnabled); + NODE_WRITE_NUM_T ( this_node, pjmedia_srtp_use, srtpUse); + NODE_WRITE_INT ( this_node, srtpSecureSignaling); + NODE_WRITE_NUM_T ( this_node, pjsua_ipv6_use, ipv6Use); + NODE_WRITE_OBJ ( this_node, transportConfig); +} + +/////////////////////////////////////////////////////////////////////////////// + +void AccountVideoConfig::readObject(const ContainerNode &node) throw(Error) +{ + ContainerNode this_node = node.readContainer("AccountVideoConfig"); + + NODE_READ_BOOL ( this_node, autoShowIncoming); + NODE_READ_BOOL ( this_node, autoTransmitOutgoing); + NODE_READ_UNSIGNED( this_node, windowFlags); + NODE_READ_NUM_T ( this_node, pjmedia_vid_dev_index, defaultCaptureDevice); + NODE_READ_NUM_T ( this_node, pjmedia_vid_dev_index, defaultRenderDevice); + NODE_READ_NUM_T ( this_node, pjmedia_vid_stream_rc_method, rateControlMethod); + NODE_READ_UNSIGNED( this_node, rateControlBandwidth); +} + +void AccountVideoConfig::writeObject(ContainerNode &node) const throw(Error) +{ + ContainerNode this_node = node.writeNewContainer("AccountVideoConfig"); + + NODE_WRITE_BOOL ( this_node, autoShowIncoming); + NODE_WRITE_BOOL ( this_node, autoTransmitOutgoing); + NODE_WRITE_UNSIGNED( this_node, windowFlags); + NODE_WRITE_NUM_T ( this_node, pjmedia_vid_dev_index, defaultCaptureDevice); + NODE_WRITE_NUM_T ( this_node, pjmedia_vid_dev_index, defaultRenderDevice); + NODE_WRITE_NUM_T ( this_node, pjmedia_vid_stream_rc_method, rateControlMethod); + NODE_WRITE_UNSIGNED( this_node, rateControlBandwidth); +} + +/////////////////////////////////////////////////////////////////////////////// + +AccountConfig::AccountConfig() +{ + pjsua_acc_config acc_cfg; + pjsua_acc_config_default(&acc_cfg); + pjsua_media_config med_cfg; + pjsua_media_config_default(&med_cfg); + fromPj(acc_cfg, &med_cfg); +} + +/* Convert to pjsip. */ +void AccountConfig::toPj(pjsua_acc_config &ret) const +{ + unsigned i; + + pjsua_acc_config_default(&ret); + + // Global + ret.priority = priority; + ret.id = str2Pj(idUri); + + // AccountRegConfig + ret.reg_uri = str2Pj(regConfig.registrarUri); + ret.register_on_acc_add = regConfig.registerOnAdd; + ret.reg_timeout = regConfig.timeoutSec; + ret.reg_retry_interval = regConfig.retryIntervalSec; + ret.reg_first_retry_interval= regConfig.firstRetryIntervalSec; + ret.reg_delay_before_refresh= regConfig.delayBeforeRefreshSec; + ret.drop_calls_on_reg_fail = regConfig.dropCallsOnFail; + ret.unreg_timeout = regConfig.unregWaitSec; + ret.reg_use_proxy = regConfig.proxyUse; + for (i=0; i<regConfig.headers.size(); ++i) { + pj_list_push_back(&ret.reg_hdr_list, ®Config.headers[i].toPj()); + } + + // AccountSipConfig + ret.cred_count = 0; + if (sipConfig.authCreds.size() > PJ_ARRAY_SIZE(ret.cred_info)) + PJSUA2_RAISE_ERROR(PJ_ETOOMANY); + for (i=0; i<sipConfig.authCreds.size(); ++i) { + const AuthCredInfo &src = sipConfig.authCreds[i]; + pjsip_cred_info *dst = &ret.cred_info[i]; + + dst->realm = str2Pj(src.realm); + dst->scheme = str2Pj(src.scheme); + dst->username = str2Pj(src.username); + dst->data_type = src.dataType; + dst->data = str2Pj(src.data); + dst->ext.aka.k = str2Pj(src.akaK); + dst->ext.aka.op = str2Pj(src.akaOp); + dst->ext.aka.amf= str2Pj(src.akaAmf); + + ret.cred_count++; + } + ret.proxy_cnt = 0; + if (sipConfig.proxies.size() > PJ_ARRAY_SIZE(ret.proxy)) + PJSUA2_RAISE_ERROR(PJ_ETOOMANY); + for (i=0; i<sipConfig.proxies.size(); ++i) { + ret.proxy[ret.proxy_cnt++] = str2Pj(sipConfig.proxies[i]); + } + ret.force_contact = str2Pj(sipConfig.contactForced); + ret.contact_params = str2Pj(sipConfig.contactParams); + ret.contact_uri_params = str2Pj(sipConfig.contactUriParams); + ret.auth_pref.initial_auth = sipConfig.authInitialEmpty; + ret.auth_pref.algorithm = str2Pj(sipConfig.authInitialAlgorithm); + ret.transport_id = sipConfig.transportId; + + // AccountCallConfig + ret.call_hold_type = callConfig.holdType; + ret.require_100rel = callConfig.prackUse; + ret.use_timer = callConfig.timerUse; + ret.timer_setting.min_se = callConfig.timerMinSESec; + ret.timer_setting.sess_expires = callConfig.timerSessExpiresSec; + + // AccountPresConfig + for (i=0; i<presConfig.headers.size(); ++i) { + pj_list_push_back(&ret.sub_hdr_list, &presConfig.headers[i].toPj()); + } + ret.publish_enabled = presConfig.publishEnabled; + ret.publish_opt.queue_request= presConfig.publishQueue; + ret.unpublish_max_wait_time_msec = presConfig.publishShutdownWaitMsec; + ret.pidf_tuple_id = str2Pj(presConfig.pidfTupleId); + + // AccountNatConfig + ret.sip_stun_use = natConfig.sipStunUse; + ret.media_stun_use = natConfig.mediaStunUse; + ret.ice_cfg_use = PJSUA_ICE_CONFIG_USE_CUSTOM; + ret.ice_cfg.enable_ice = natConfig.iceEnabled; + ret.ice_cfg.ice_max_host_cands = natConfig.iceMaxHostCands; + ret.ice_cfg.ice_opt.aggressive = natConfig.iceAggressiveNomination; + ret.ice_cfg.ice_opt.nominated_check_delay = natConfig.iceNominatedCheckDelayMsec; + ret.ice_cfg.ice_opt.controlled_agent_want_nom_timeout = natConfig.iceWaitNominationTimeoutMsec; + ret.ice_cfg.ice_no_rtcp = natConfig.iceNoRtcp; + ret.ice_cfg.ice_always_update = natConfig.iceAlwaysUpdate; + + ret.turn_cfg_use = PJSUA_TURN_CONFIG_USE_CUSTOM; + ret.turn_cfg.enable_turn = natConfig.turnEnabled; + ret.turn_cfg.turn_server = str2Pj(natConfig.turnServer); + ret.turn_cfg.turn_conn_type = natConfig.turnConnType; + ret.turn_cfg.turn_auth_cred.type = PJ_STUN_AUTH_CRED_STATIC; + ret.turn_cfg.turn_auth_cred.data.static_cred.username = str2Pj(natConfig.turnUserName); + ret.turn_cfg.turn_auth_cred.data.static_cred.data_type = (pj_stun_passwd_type)natConfig.turnPasswordType; + ret.turn_cfg.turn_auth_cred.data.static_cred.data = str2Pj(natConfig.turnPassword); + ret.turn_cfg.turn_auth_cred.data.static_cred.realm = pj_str((char*)""); + ret.turn_cfg.turn_auth_cred.data.static_cred.nonce = pj_str((char*)""); + + ret.allow_contact_rewrite = natConfig.contactRewriteUse; + ret.contact_rewrite_method = natConfig.contactRewriteMethod; + ret.allow_via_rewrite = natConfig.viaRewriteUse; + ret.allow_sdp_nat_rewrite = natConfig.sdpNatRewriteUse; + ret.use_rfc5626 = natConfig.sipOutboundUse; + ret.rfc5626_instance_id = str2Pj(natConfig.sipOutboundInstanceId); + ret.rfc5626_reg_id = str2Pj(natConfig.sipOutboundRegId); + ret.ka_interval = natConfig.udpKaIntervalSec; + ret.ka_data = str2Pj(natConfig.udpKaData); + + // AccountMediaConfig + ret.rtp_cfg = mediaConfig.transportConfig.toPj(); + ret.lock_codec = mediaConfig.lockCodecEnabled; +#if defined(PJMEDIA_STREAM_ENABLE_KA) && (PJMEDIA_STREAM_ENABLE_KA != 0) + ret.use_stream_ka = mediaConfig.streamKaEnabled; +#endif + ret.use_srtp = mediaConfig.srtpUse; + ret.srtp_secure_signaling = mediaConfig.srtpSecureSignaling; + ret.ipv6_media_use = mediaConfig.ipv6Use; + + // AccountVideoConfig + ret.vid_in_auto_show = videoConfig.autoShowIncoming; + ret.vid_out_auto_transmit = videoConfig.autoTransmitOutgoing; + ret.vid_wnd_flags = videoConfig.windowFlags; + ret.vid_cap_dev = videoConfig.defaultCaptureDevice; + ret.vid_rend_dev = videoConfig.defaultRenderDevice; + ret.vid_stream_rc_cfg.method= videoConfig.rateControlMethod; + ret.vid_stream_rc_cfg.bandwidth = videoConfig.rateControlBandwidth; +} + +/* Initialize from pjsip. */ +void AccountConfig::fromPj(const pjsua_acc_config &prm, + const pjsua_media_config *mcfg) +{ + const pjsip_hdr *hdr; + unsigned i; + + // Global + priority = prm.priority; + idUri = pj2Str(prm.id); + + // AccountRegConfig + regConfig.registrarUri = pj2Str(prm.reg_uri); + regConfig.registerOnAdd = (prm.register_on_acc_add != 0); + regConfig.timeoutSec = prm.reg_timeout; + regConfig.retryIntervalSec = prm.reg_retry_interval; + regConfig.firstRetryIntervalSec = prm.reg_first_retry_interval; + regConfig.delayBeforeRefreshSec = prm.reg_delay_before_refresh; + regConfig.dropCallsOnFail = PJ2BOOL(prm.drop_calls_on_reg_fail); + regConfig.unregWaitSec = prm.unreg_timeout; + regConfig.proxyUse = prm.reg_use_proxy; + regConfig.headers.clear(); + hdr = prm.reg_hdr_list.next; + while (hdr != &prm.reg_hdr_list) { + SipHeader new_hdr; + new_hdr.fromPj(hdr); + + regConfig.headers.push_back(new_hdr); + + hdr = hdr->next; + } + + // AccountSipConfig + sipConfig.authCreds.clear(); + for (i=0; i<prm.cred_count; ++i) { + AuthCredInfo cred; + const pjsip_cred_info &src = prm.cred_info[i]; + + cred.realm = pj2Str(src.realm); + cred.scheme = pj2Str(src.scheme); + cred.username = pj2Str(src.username); + cred.dataType = src.data_type; + cred.data = pj2Str(src.data); + cred.akaK = pj2Str(src.ext.aka.k); + cred.akaOp = pj2Str(src.ext.aka.op); + cred.akaAmf = pj2Str(src.ext.aka.amf); + + sipConfig.authCreds.push_back(cred); + } + sipConfig.proxies.clear(); + for (i=0; i<prm.proxy_cnt; ++i) { + sipConfig.proxies.push_back(pj2Str(prm.proxy[i])); + } + sipConfig.contactForced = pj2Str(prm.force_contact); + sipConfig.contactParams = pj2Str(prm.contact_params); + sipConfig.contactUriParams = pj2Str(prm.contact_uri_params); + sipConfig.authInitialEmpty = PJ2BOOL(prm.auth_pref.initial_auth); + sipConfig.authInitialAlgorithm = pj2Str(prm.auth_pref.algorithm); + sipConfig.transportId = prm.transport_id; + + // AccountCallConfig + callConfig.holdType = prm.call_hold_type; + callConfig.prackUse = prm.require_100rel; + callConfig.timerUse = prm.use_timer; + callConfig.timerMinSESec = prm.timer_setting.min_se; + callConfig.timerSessExpiresSec = prm.timer_setting.sess_expires; + + // AccountPresConfig + presConfig.headers.clear(); + hdr = prm.sub_hdr_list.next; + while (hdr != &prm.sub_hdr_list) { + SipHeader new_hdr; + new_hdr.fromPj(hdr); + presConfig.headers.push_back(new_hdr); + hdr = hdr->next; + } + presConfig.publishEnabled = PJ2BOOL(prm.publish_enabled); + presConfig.publishQueue = PJ2BOOL(prm.publish_opt.queue_request); + presConfig.publishShutdownWaitMsec = prm.unpublish_max_wait_time_msec; + presConfig.pidfTupleId = pj2Str(prm.pidf_tuple_id); + + // AccountMwiConfig + mwiConfig.enabled = PJ2BOOL(prm.mwi_enabled); + mwiConfig.expirationSec = prm.mwi_expires; + + // AccountNatConfig + natConfig.sipStunUse = prm.sip_stun_use; + natConfig.mediaStunUse = prm.media_stun_use; + if (prm.ice_cfg_use == PJSUA_ICE_CONFIG_USE_CUSTOM) { + natConfig.iceEnabled = PJ2BOOL(prm.ice_cfg.enable_ice); + natConfig.iceMaxHostCands = prm.ice_cfg.ice_max_host_cands; + natConfig.iceAggressiveNomination = PJ2BOOL(prm.ice_cfg.ice_opt.aggressive); + natConfig.iceNominatedCheckDelayMsec = prm.ice_cfg.ice_opt.nominated_check_delay; + natConfig.iceWaitNominationTimeoutMsec = prm.ice_cfg.ice_opt.controlled_agent_want_nom_timeout; + natConfig.iceNoRtcp = PJ2BOOL(prm.ice_cfg.ice_no_rtcp); + natConfig.iceAlwaysUpdate = PJ2BOOL(prm.ice_cfg.ice_always_update); + } else { + pjsua_media_config default_mcfg; + if (!mcfg) { + pjsua_media_config_default(&default_mcfg); + mcfg = &default_mcfg; + } + natConfig.iceEnabled = PJ2BOOL(mcfg->enable_ice); + natConfig.iceMaxHostCands= mcfg->ice_max_host_cands; + natConfig.iceAggressiveNomination = PJ2BOOL(mcfg->ice_opt.aggressive); + natConfig.iceNominatedCheckDelayMsec = mcfg->ice_opt.nominated_check_delay; + natConfig.iceWaitNominationTimeoutMsec = mcfg->ice_opt.controlled_agent_want_nom_timeout; + natConfig.iceNoRtcp = PJ2BOOL(mcfg->ice_no_rtcp); + natConfig.iceAlwaysUpdate = PJ2BOOL(mcfg->ice_always_update); + } + + if (prm.turn_cfg_use == PJSUA_TURN_CONFIG_USE_CUSTOM) { + natConfig.turnEnabled = PJ2BOOL(prm.turn_cfg.enable_turn); + natConfig.turnServer = pj2Str(prm.turn_cfg.turn_server); + natConfig.turnConnType = prm.turn_cfg.turn_conn_type; + natConfig.turnUserName = pj2Str(prm.turn_cfg.turn_auth_cred.data.static_cred.username); + natConfig.turnPasswordType = prm.turn_cfg.turn_auth_cred.data.static_cred.data_type; + natConfig.turnPassword = pj2Str(prm.turn_cfg.turn_auth_cred.data.static_cred.data); + } else { + pjsua_media_config default_mcfg; + if (!mcfg) { + pjsua_media_config_default(&default_mcfg); + mcfg = &default_mcfg; + } + natConfig.turnEnabled = PJ2BOOL(mcfg->enable_turn); + natConfig.turnServer = pj2Str(mcfg->turn_server); + natConfig.turnConnType = mcfg->turn_conn_type; + natConfig.turnUserName = pj2Str(mcfg->turn_auth_cred.data.static_cred.username); + natConfig.turnPasswordType = mcfg->turn_auth_cred.data.static_cred.data_type; + natConfig.turnPassword = pj2Str(mcfg->turn_auth_cred.data.static_cred.data); + } + natConfig.contactRewriteUse = prm.allow_contact_rewrite; + natConfig.contactRewriteMethod = prm.contact_rewrite_method; + natConfig.viaRewriteUse = prm.allow_via_rewrite; + natConfig.sdpNatRewriteUse = prm.allow_sdp_nat_rewrite; + natConfig.sipOutboundUse = prm.use_rfc5626; + natConfig.sipOutboundInstanceId = pj2Str(prm.rfc5626_instance_id); + natConfig.sipOutboundRegId = pj2Str(prm.rfc5626_reg_id); + natConfig.udpKaIntervalSec = prm.ka_interval; + natConfig.udpKaData = pj2Str(prm.ka_data); + + // AccountMediaConfig + mediaConfig.transportConfig.fromPj(prm.rtp_cfg); + mediaConfig.lockCodecEnabled= PJ2BOOL(prm.lock_codec); +#if defined(PJMEDIA_STREAM_ENABLE_KA) && (PJMEDIA_STREAM_ENABLE_KA != 0) + mediaConfig.streamKaEnabled = PJ2BOOL(prm.use_stream_ka); +#else + mediaConfig.streamKaEnabled = false; +#endif + mediaConfig.srtpUse = prm.use_srtp; + mediaConfig.srtpSecureSignaling = prm.srtp_secure_signaling; + mediaConfig.ipv6Use = prm.ipv6_media_use; + + // AccountVideoConfig + videoConfig.autoShowIncoming = PJ2BOOL(prm.vid_in_auto_show); + videoConfig.autoTransmitOutgoing = PJ2BOOL(prm.vid_out_auto_transmit); + videoConfig.windowFlags = prm.vid_wnd_flags; + videoConfig.defaultCaptureDevice = prm.vid_cap_dev; + videoConfig.defaultRenderDevice = prm.vid_rend_dev; + videoConfig.rateControlMethod = prm.vid_stream_rc_cfg.method; + videoConfig.rateControlBandwidth = prm.vid_stream_rc_cfg.bandwidth; +} + +void AccountConfig::readObject(const ContainerNode &node) throw(Error) +{ + ContainerNode this_node = node.readContainer("AccountConfig"); + + NODE_READ_INT ( this_node, priority); + NODE_READ_STRING ( this_node, idUri); + NODE_READ_OBJ ( this_node, regConfig); + NODE_READ_OBJ ( this_node, sipConfig); + NODE_READ_OBJ ( this_node, callConfig); + NODE_READ_OBJ ( this_node, presConfig); + NODE_READ_OBJ ( this_node, mwiConfig); + NODE_READ_OBJ ( this_node, natConfig); + NODE_READ_OBJ ( this_node, mediaConfig); + NODE_READ_OBJ ( this_node, videoConfig); +} + +void AccountConfig::writeObject(ContainerNode &node) const throw(Error) +{ + ContainerNode this_node = node.writeNewContainer("AccountConfig"); + + NODE_WRITE_INT ( this_node, priority); + NODE_WRITE_STRING ( this_node, idUri); + NODE_WRITE_OBJ ( this_node, regConfig); + NODE_WRITE_OBJ ( this_node, sipConfig); + NODE_WRITE_OBJ ( this_node, callConfig); + NODE_WRITE_OBJ ( this_node, presConfig); + NODE_WRITE_OBJ ( this_node, mwiConfig); + NODE_WRITE_OBJ ( this_node, natConfig); + NODE_WRITE_OBJ ( this_node, mediaConfig); + NODE_WRITE_OBJ ( this_node, videoConfig); +} + + +/////////////////////////////////////////////////////////////////////////////// + +void AccountInfo::fromPj(const pjsua_acc_info &pai) +{ + id = pai.id; + isDefault = pai.is_default != 0; + uri = pj2Str(pai.acc_uri); + regIsConfigured = pai.has_registration != 0; + regIsActive = pai.has_registration && pai.expires > 0 && + (pai.status / 100 == 2); + regExpiresSec = pai.expires; + regStatus = pai.status; + regStatusText = pj2Str(pai.status_text); + regLastErr = pai.reg_last_err; + onlineStatus = pai.online_status != 0; + onlineStatusText = pj2Str(pai.online_status_text); +} + +/////////////////////////////////////////////////////////////////////////////// + +Account::Account() +: id(PJSUA_INVALID_ID) +{ +} + +Account::~Account() +{ + /* If this instance is deleted, also delete the corresponding account in + * PJSUA library. + */ + if (isValid() && pjsua_get_state() < PJSUA_STATE_CLOSING) { + // Cleanup buddies in the buddy list + while(buddyList.size() > 0) { + Buddy *b = buddyList[0]; + delete b; /* this will remove itself from the list */ + } + + pjsua_acc_set_user_data(id, NULL); + pjsua_acc_del(id); + } +} + +void Account::create(const AccountConfig &acc_cfg, + bool make_default) throw(Error) +{ + pjsua_acc_config pj_acc_cfg; + + acc_cfg.toPj(pj_acc_cfg); + pj_acc_cfg.user_data = (void*)this; + PJSUA2_CHECK_EXPR( pjsua_acc_add(&pj_acc_cfg, make_default, &id) ); +} + +void Account::modify(const AccountConfig &acc_cfg) throw(Error) +{ + pjsua_acc_config pj_acc_cfg; + + acc_cfg.toPj(pj_acc_cfg); + pj_acc_cfg.user_data = (void*)this; + PJSUA2_CHECK_EXPR( pjsua_acc_modify(id, &pj_acc_cfg) ); +} + +bool Account::isValid() const +{ + return pjsua_acc_is_valid(id) != 0; +} + +void Account::setDefault() throw(Error) +{ + PJSUA2_CHECK_EXPR( pjsua_acc_set_default(id) ); +} + +bool Account::isDefault() const +{ + return pjsua_acc_get_default() == id; +} + +int Account::getId() const +{ + return id; +} + +Account *Account::lookup(int acc_id) +{ + return (Account*)pjsua_acc_get_user_data(acc_id); +} + +AccountInfo Account::getInfo() const throw(Error) +{ + pjsua_acc_info pj_ai; + AccountInfo ai; + + PJSUA2_CHECK_EXPR( pjsua_acc_get_info(id, &pj_ai) ); + ai.fromPj(pj_ai); + return ai; +} + +void Account::setRegistration(bool renew) throw(Error) +{ + PJSUA2_CHECK_EXPR( pjsua_acc_set_registration(id, renew) ); +} + +void +Account::setOnlineStatus(const PresenceStatus &pres_st) throw(Error) +{ + pjrpid_element pj_rpid; + + pj_bzero(&pj_rpid, sizeof(pj_rpid)); + pj_rpid.type = PJRPID_ELEMENT_TYPE_PERSON; + pj_rpid.activity = pres_st.activity; + pj_rpid.id = str2Pj(pres_st.rpidId); + pj_rpid.note = str2Pj(pres_st.note); + + PJSUA2_CHECK_EXPR( pjsua_acc_set_online_status2( + id, pres_st.status == PJSUA_BUDDY_STATUS_ONLINE, + &pj_rpid) ); +} + +void Account::setTransport(TransportId tp_id) throw(Error) +{ + PJSUA2_CHECK_EXPR( pjsua_acc_set_transport(id, tp_id) ); +} + +void Account::presNotify(const PresNotifyParam &prm) throw(Error) +{ + pj_str_t pj_state_str = str2Pj(prm.stateStr); + pj_str_t pj_reason = str2Pj(prm.reason); + pjsua_msg_data msg_data; + prm.txOption.toPj(msg_data); + + PJSUA2_CHECK_EXPR( pjsua_pres_notify(id, (pjsua_srv_pres*)prm.srvPres, + prm.state, &pj_state_str, + &pj_reason, prm.withBody, + &msg_data) ); +} + +const BuddyVector& Account::enumBuddies() const throw(Error) +{ + return buddyList; +} + +Buddy* Account::findBuddy(string uri, FindBuddyMatch *buddy_match) const + throw(Error) +{ + if (!buddy_match) { + static FindBuddyMatch def_bm; + buddy_match = &def_bm; + } + + for (unsigned i = 0; i < buddyList.size(); i++) { + if (buddy_match->match(uri, *buddyList[i])) + return buddyList[i]; + } + PJSUA2_RAISE_ERROR(PJ_ENOTFOUND); +} + +void Account::addBuddy(Buddy *buddy) +{ + pj_assert(buddy); + + buddyList.push_back(buddy); +} + +void Account::removeBuddy(Buddy *buddy) +{ + pj_assert(buddy); + + BuddyVector::iterator it; + for (it = buddyList.begin(); it != buddyList.end(); it++) { + if (*it == buddy) { + buddyList.erase(it); + return; + } + } + + pj_assert(!"Bug! Buddy to be removed is not in the buddy list!"); +} diff --git a/pjsip/src/pjsua2/call.cpp b/pjsip/src/pjsua2/call.cpp new file mode 100644 index 00000000..69a91829 --- /dev/null +++ b/pjsip/src/pjsua2/call.cpp @@ -0,0 +1,719 @@ +/* $Id$ */ +/* + * Copyright (C) 2012-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/account.hpp> +#include <pjsua2/call.hpp> +#include <pjsua2/endpoint.hpp> +#include <pj/ctype.h> +#include "util.hpp" + +using namespace pj; +using namespace std; + +#define THIS_FILE "call.cpp" + +/////////////////////////////////////////////////////////////////////////////// + +#define SDP_BUFFER_SIZE 1024 + +MathStat::MathStat() +: n(0), max(0), min(0), last(0), mean(0) +{ +} + +void MathStat::fromPj(const pj_math_stat &prm) +{ + this->n = prm.n; + this->max = prm.max; + this->min = prm.min; + this->last = prm.last; + this->mean = prm.mean; +} + +void RtcpStreamStat::fromPj(const pjmedia_rtcp_stream_stat &prm) +{ + this->update.fromPj(prm.update); + this->updateCount = prm.update_cnt; + this->pkt = (unsigned)prm.pkt; + this->bytes = (unsigned)prm.bytes; + this->discard = prm.discard; + this->loss = prm.loss; + this->reorder = prm.loss; + this->dup = prm.dup; + this->lossPeriodUsec.fromPj(prm.loss_period); + this->lossType.burst = prm.loss_type.burst; + this->lossType.random = prm.loss_type.random; + this->jitterUsec.fromPj(prm.jitter); +} + +void RtcpSdes::fromPj(const pjmedia_rtcp_sdes &prm) +{ + this->cname = pj2Str(prm.cname); + this->name = pj2Str(prm.name); + this->email = pj2Str(prm.email); + this->phone = pj2Str(prm.phone); + this->loc = pj2Str(prm.loc); + this->tool = pj2Str(prm.tool); + this->note = pj2Str(prm.note); +} + +void RtcpStat::fromPj(const pjmedia_rtcp_stat &prm) +{ + this->start.fromPj(prm.start); + this->txStat.fromPj(prm.tx); + this->rxStat.fromPj(prm.rx); + this->rttUsec.fromPj(prm.rtt); + this->rtpTxLastTs = prm.rtp_tx_last_ts; + this->rtpTxLastSeq = prm.rtp_tx_last_seq; +#if defined(PJMEDIA_RTCP_STAT_HAS_IPDV) && PJMEDIA_RTCP_STAT_HAS_IPDV!=0 + this->rxIpdvUsec.fromPj(prm.rx_ipdv); +#endif +#if defined(PJMEDIA_RTCP_STAT_HAS_RAW_JITTER) && \ + PJMEDIA_RTCP_STAT_HAS_RAW_JITTER!=0 + this->rxRawJitterUsec.fromPj(prm.rx_raw_jitter); +#endif + this->peerSdes.fromPj(prm.peer_sdes); +} + +void JbufState::fromPj(const pjmedia_jb_state &prm) +{ + this->frameSize = prm.frame_size; + this->minPrefetch = prm.min_prefetch; + this->maxPrefetch = prm.max_prefetch; + this->burst = prm.burst; + this->prefetch = prm.prefetch; + this->size = prm.size; + this->avgDelayMsec = prm.avg_delay; + this->minDelayMsec = prm.min_delay; + this->maxDelayMsec = prm.max_delay; + this->devDelayMsec = prm.dev_delay; + this->avgBurst = prm.avg_burst; + this->lost = prm.lost; + this->discard = prm.discard; + this->empty = prm.empty; +} + +void SdpSession::fromPj(const pjmedia_sdp_session &sdp) +{ + char buf[SDP_BUFFER_SIZE]; + int len; + + len = pjmedia_sdp_print(&sdp, buf, sizeof(buf)); + wholeSdp = (len > -1? string(buf, len): ""); + pjSdpSession = (void *)&sdp; +} + +void MediaEvent::fromPj(const pjmedia_event &ev) +{ + type = ev.type; + if (type == PJMEDIA_EVENT_FMT_CHANGED) { + data.fmtChanged.newWidth = ev.data.fmt_changed.new_fmt.det.vid.size.w; + data.fmtChanged.newHeight = ev.data.fmt_changed.new_fmt.det.vid.size.h; + } + pjMediaEvent = (void *)&ev; +} + +void MediaTransportInfo::fromPj(const pjmedia_transport_info &info) +{ + char straddr[PJ_INET6_ADDRSTRLEN+10]; + + pj_sockaddr_print(&info.src_rtp_name, straddr, sizeof(straddr), 3); + srcRtpName = straddr; + pj_sockaddr_print(&info.src_rtcp_name, straddr, sizeof(straddr), 3); + srcRtcpName = straddr; +} + +////////////////////////////////////////////////////////////////////////////// + +/* Call Audio Media. */ +class CallAudioMedia : public AudioMedia +{ +public: + /* + * Set the conference port identification associated with the + * call audio media. + */ + void setPortId(int id); +}; + + +void CallAudioMedia::setPortId(int id) +{ + this->id = id; +} + +CallOpParam::CallOpParam(bool useDefaultCallSetting) +: statusCode(pjsip_status_code(0)), reason(""), options(0) +{ + if (useDefaultCallSetting) + opt = CallSetting(true); +} + +CallSendRequestParam::CallSendRequestParam() +: method("") +{ +} + +CallVidSetStreamParam::CallVidSetStreamParam() +{ +#if PJSUA_HAS_VIDEO + pjsua_call_vid_strm_op_param prm; + + pjsua_call_vid_strm_op_param_default(&prm); + this->medIdx = prm.med_idx; + this->dir = prm.dir; + this->capDev = prm.cap_dev; +#endif +} + +CallSetting::CallSetting(pj_bool_t useDefaultValues) +{ + if (useDefaultValues) { + pjsua_call_setting setting; + + pjsua_call_setting_default(&setting); + fromPj(setting); + } else { + flag = 0; + reqKeyframeMethod = 0; + audioCount = 0; + videoCount = 0; + } +} + +bool CallSetting::isEmpty() const +{ + return (flag == 0 && reqKeyframeMethod == 0 && audioCount == 0 && + videoCount == 0); +} + +void CallSetting::fromPj(const pjsua_call_setting &prm) +{ + this->flag = prm.flag; + this->reqKeyframeMethod = prm.req_keyframe_method; + this->audioCount = prm.aud_cnt; + this->videoCount = prm.vid_cnt; +} + +pjsua_call_setting CallSetting::toPj() const +{ + pjsua_call_setting setting; + + setting.flag = this->flag; + setting.req_keyframe_method = this->reqKeyframeMethod; + setting.aud_cnt = this->audioCount; + setting.vid_cnt = this->videoCount; + + return setting; +} + + +CallMediaInfo::CallMediaInfo() +{ +} + +void CallMediaInfo::fromPj(const pjsua_call_media_info &prm) +{ + this->index = prm.index; + this->type = prm.type; + this->dir = prm.dir; + this->status = prm.status; + if (this->type == PJMEDIA_TYPE_AUDIO) { + this->audioConfSlot = (int)prm.stream.aud.conf_slot; + } else if (this->type == PJMEDIA_TYPE_VIDEO) { + this->videoIncomingWindowId = prm.stream.vid.win_in; + this->videoCapDev = prm.stream.vid.cap_dev; + } +} + +void CallInfo::fromPj(const pjsua_call_info &pci) +{ + unsigned mi; + + id = pci.id; + role = pci.role; + accId = pci.acc_id; + localUri = pj2Str(pci.local_info); + localContact = pj2Str(pci.local_contact); + remoteUri = pj2Str(pci.remote_info); + remoteContact = pj2Str(pci.remote_contact); + callIdString = pj2Str(pci.call_id); + setting.fromPj(pci.setting); + state = pci.state; + stateText = pj2Str(pci.state_text); + lastStatusCode = pci.last_status; + lastReason = pj2Str(pci.last_status_text); + connectDuration.fromPj(pci.connect_duration); + totalDuration.fromPj(pci.total_duration); + remOfferer = PJ2BOOL(pci.rem_offerer); + remAudioCount = pci.rem_aud_cnt; + remVideoCount = pci.rem_vid_cnt; + + for (mi = 0; mi < pci.media_cnt; mi++) { + CallMediaInfo med; + + med.fromPj(pci.media[mi]); + media.push_back(med); + } + for (mi = 0; mi < pci.prov_media_cnt; mi++) { + CallMediaInfo med; + + med.fromPj(pci.prov_media[mi]); + provMedia.push_back(med); + } +} + +void StreamInfo::fromPj(const pjsua_stream_info &info) +{ + char straddr[PJ_INET6_ADDRSTRLEN+10]; + + type = info.type; + if (type == PJMEDIA_TYPE_AUDIO) { + proto = info.info.aud.proto; + dir = info.info.aud.dir; + pj_sockaddr_print(&info.info.aud.rem_addr, straddr, sizeof(straddr), 3); + remoteRtpAddress = straddr; + pj_sockaddr_print(&info.info.aud.rem_rtcp, straddr, sizeof(straddr), 3); + remoteRtcpAddress = straddr; + txPt = info.info.aud.tx_pt; + rxPt = info.info.aud.rx_pt; + codecName = pj2Str(info.info.aud.fmt.encoding_name); + codecClockRate = info.info.aud.fmt.clock_rate; + codecParam = info.info.aud.param; + } else if (type == PJMEDIA_TYPE_VIDEO) { + proto = info.info.vid.proto; + dir = info.info.vid.dir; + pj_sockaddr_print(&info.info.vid.rem_addr, straddr, sizeof(straddr), 3); + remoteRtpAddress = straddr; + pj_sockaddr_print(&info.info.vid.rem_rtcp, straddr, sizeof(straddr), 3); + remoteRtcpAddress = straddr; + txPt = info.info.vid.tx_pt; + rxPt = info.info.vid.rx_pt; + codecName = pj2Str(info.info.vid.codec_info.encoding_name); + codecClockRate = info.info.vid.codec_info.clock_rate; + codecParam = info.info.vid.codec_param; + } +} + +void StreamStat::fromPj(const pjsua_stream_stat &prm) +{ + rtcp.fromPj(prm.rtcp); + jbuf.fromPj(prm.jbuf); +} + +/////////////////////////////////////////////////////////////////////////////// + +struct call_param +{ + pjsua_msg_data msg_data; + pjsua_msg_data *p_msg_data; + pjsua_call_setting opt; + pjsua_call_setting *p_opt; + pj_str_t reason; + pj_str_t *p_reason; + +public: + /** + * Default constructors with specified parameters. + */ + call_param(const SipTxOption &tx_option); + call_param(const SipTxOption &tx_option, const CallSetting &setting, + const string &reason_str); +}; + +call_param::call_param(const SipTxOption &tx_option) +{ + if (tx_option.isEmpty()) { + p_msg_data = NULL; + } else { + tx_option.toPj(msg_data); + p_msg_data = &msg_data; + } + + p_opt = NULL; + p_reason = NULL; +} + +call_param::call_param(const SipTxOption &tx_option, const CallSetting &setting, + const string &reason_str) +{ + if (tx_option.isEmpty()) { + p_msg_data = NULL; + } else { + tx_option.toPj(msg_data); + p_msg_data = &msg_data; + } + + if (setting.isEmpty()) { + p_opt = NULL; + } else { + opt = setting.toPj(); + p_opt = &opt; + } + + reason = str2Pj(reason_str); + p_reason = (reason.slen == 0? NULL: &reason); +} + +Call::Call(Account& account, int call_id) +: acc(account), id(call_id) +{ + if (call_id != PJSUA_INVALID_ID) + pjsua_call_set_user_data(call_id, this); +} + +Call::~Call() +{ + /** + * If this instance is deleted, also hangup the corresponding call in + * PJSUA library. + */ + if (id != PJSUA_INVALID_ID && pjsua_get_state() < PJSUA_STATE_CLOSING) { + pjsua_call_set_user_data(id, NULL); + if (isActive()) { + CallOpParam prm; + hangup(prm); + } + } +} + +CallInfo Call::getInfo() const throw(Error) +{ + pjsua_call_info pj_ci; + CallInfo ci; + + PJSUA2_CHECK_EXPR( pjsua_call_get_info(id, &pj_ci) ); + ci.fromPj(pj_ci); + return ci; +} + +bool Call::isActive() const +{ + if (id == PJSUA_INVALID_ID) + return false; + + return (pjsua_call_is_active(id) != 0); +} + +int Call::getId() const +{ + return id; +} + +Call *Call::lookup(int call_id) +{ + Call *call = (Call*)pjsua_call_get_user_data(call_id); + if (call) + call->id = call_id; + return call; +} + +bool Call::hasMedia() const +{ + return (pjsua_call_has_media(id) != 0); +} + +Media *Call::getMedia(unsigned med_idx) const +{ + /* Check if the media index is valid and if the media has a valid port ID */ + if (med_idx >= medias.size() || + (medias[med_idx] && medias[med_idx]->getType() == PJMEDIA_TYPE_AUDIO && + ((AudioMedia *)medias[med_idx])->getPortId() == PJSUA_INVALID_ID)) + { + return NULL; + } + + return medias[med_idx]; +} + +pjsip_dialog_cap_status Call::remoteHasCap(int htype, + const string &hname, + const string &token) const +{ + pj_str_t pj_hname = str2Pj(hname); + pj_str_t pj_token = str2Pj(token); + + return pjsua_call_remote_has_cap(id, htype, + (htype == PJSIP_H_OTHER)? &pj_hname: NULL, + &pj_token); +} + +void Call::setUserData(Token user_data) +{ + userData = user_data; +} + +Token Call::getUserData() const +{ + return userData; +} + +pj_stun_nat_type Call::getRemNatType() throw(Error) +{ + pj_stun_nat_type nat; + + PJSUA2_CHECK_EXPR( pjsua_call_get_rem_nat_type(id, &nat) ); + + return nat; +} + +void Call::makeCall(const string &dst_uri, const CallOpParam &prm) throw(Error) +{ + pj_str_t pj_dst_uri = str2Pj(dst_uri); + call_param param(prm.txOption, prm.opt, prm.reason); + + PJSUA2_CHECK_EXPR( pjsua_call_make_call(acc.getId(), &pj_dst_uri, + param.p_opt, this, + param.p_msg_data, &id) ); +} + +void Call::answer(const CallOpParam &prm) throw(Error) +{ + call_param param(prm.txOption, prm.opt, prm.reason); + + PJSUA2_CHECK_EXPR( pjsua_call_answer2(id, param.p_opt, prm.statusCode, + param.p_reason, param.p_msg_data) ); +} + +void Call::hangup(const CallOpParam &prm) throw(Error) +{ + call_param param(prm.txOption, prm.opt, prm.reason); + + PJSUA2_CHECK_EXPR( pjsua_call_hangup(id, prm.statusCode, param.p_reason, + param.p_msg_data) ); +} + +void Call::setHold(const CallOpParam &prm) throw(Error) +{ + call_param param(prm.txOption, prm.opt, prm.reason); + + PJSUA2_CHECK_EXPR( pjsua_call_set_hold2(id, prm.options, param.p_msg_data)); +} + +void Call::reinvite(const CallOpParam &prm) throw(Error) +{ + call_param param(prm.txOption, prm.opt, prm.reason); + + PJSUA2_CHECK_EXPR( pjsua_call_reinvite2(id, param.p_opt, param.p_msg_data)); +} + +void Call::update(const CallOpParam &prm) throw(Error) +{ + call_param param(prm.txOption, prm.opt, prm.reason); + + PJSUA2_CHECK_EXPR( pjsua_call_update2(id, param.p_opt, param.p_msg_data) ); +} + +void Call::xfer(const string &dest, const CallOpParam &prm) throw(Error) +{ + call_param param(prm.txOption); + pj_str_t pj_dest = str2Pj(dest); + + PJSUA2_CHECK_EXPR( pjsua_call_xfer(id, &pj_dest, param.p_msg_data) ); +} + +void Call::xferReplaces(const Call& dest_call, + const CallOpParam &prm) throw(Error) +{ + call_param param(prm.txOption); + + PJSUA2_CHECK_EXPR(pjsua_call_xfer_replaces(id, dest_call.getId(), + prm.options, param.p_msg_data) ); +} + +void Call::processRedirect(pjsip_redirect_op cmd) throw(Error) +{ + PJSUA2_CHECK_EXPR(pjsua_call_process_redirect(id, cmd)); +} + +void Call::dialDtmf(const string &digits) throw(Error) +{ + pj_str_t pj_digits = str2Pj(digits); + + PJSUA2_CHECK_EXPR(pjsua_call_dial_dtmf(id, &pj_digits)); +} + +void Call::sendInstantMessage(const SendInstantMessageParam& prm) + throw(Error) +{ + pj_str_t mime_type = str2Pj(prm.contentType); + pj_str_t content = str2Pj(prm.content); + call_param param(prm.txOption); + + PJSUA2_CHECK_EXPR(pjsua_call_send_im(id, &mime_type, &content, + param.p_msg_data, prm.userData) ); +} + +void Call::sendTypingIndication(const SendTypingIndicationParam &prm) + throw(Error) +{ + call_param param(prm.txOption); + + PJSUA2_CHECK_EXPR(pjsua_call_send_typing_ind(id, + (prm.isTyping? + PJ_TRUE: PJ_FALSE), + param.p_msg_data) ); +} + +void Call::sendRequest(const CallSendRequestParam &prm) throw(Error) +{ + pj_str_t method = str2Pj(prm.method); + call_param param(prm.txOption); + + PJSUA2_CHECK_EXPR(pjsua_call_send_request(id, &method, param.p_msg_data) ); +} + +string Call::dump(bool with_media, const string indent) throw(Error) +{ +#if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0) + char buffer[1024 * 10]; +#else + char buffer[1024 * 3]; +#endif + + PJSUA2_CHECK_EXPR(pjsua_call_dump(id, (with_media? PJ_TRUE: PJ_FALSE), + buffer, sizeof(buffer), + indent.c_str())); + + return buffer; +} + +int Call::vidGetStreamIdx() const +{ +#if PJSUA_HAS_VIDEO + return pjsua_call_get_vid_stream_idx(id); +#else + return PJSUA_INVALID_ID; +#endif +} + +bool Call::vidStreamIsRunning(int med_idx, pjmedia_dir dir) const +{ +#if PJSUA_HAS_VIDEO + return pjsua_call_vid_stream_is_running(id, med_idx, dir); +#else + PJ_UNUSED_ARG(med_idx); + PJ_UNUSED_ARG(dir); + return false; +#endif +} + +void Call::vidSetStream(pjsua_call_vid_strm_op op, + const CallVidSetStreamParam ¶m) throw(Error) +{ +#if PJSUA_HAS_VIDEO + pjsua_call_vid_strm_op_param prm; + + prm.med_idx = param.medIdx; + prm.dir = param.dir; + prm.cap_dev = param.capDev; + PJSUA2_CHECK_EXPR( pjsua_call_set_vid_strm(id, op, &prm) ); +#else + PJ_UNUSED_ARG(op); + PJ_UNUSED_ARG(param); + PJSUA2_RAISE_ERROR(PJ_EINVALIDOP); +#endif +} + +StreamInfo Call::getStreamInfo(unsigned med_idx) const throw(Error) +{ + pjsua_stream_info pj_si; + StreamInfo si; + + PJSUA2_CHECK_EXPR( pjsua_call_get_stream_info(id, med_idx, &pj_si) ); + si.fromPj(pj_si); + return si; +} + +StreamStat Call::getStreamStat(unsigned med_idx) const throw(Error) +{ + pjsua_stream_stat pj_ss; + StreamStat ss; + + PJSUA2_CHECK_EXPR( pjsua_call_get_stream_stat(id, med_idx, &pj_ss) ); + ss.fromPj(pj_ss); + return ss; +} + +MediaTransportInfo Call::getMedTransportInfo(unsigned med_idx) const + throw(Error) +{ + pjmedia_transport_info pj_mti; + MediaTransportInfo mti; + + PJSUA2_CHECK_EXPR( pjsua_call_get_med_transport_info(id, med_idx, + &pj_mti) ); + mti.fromPj(pj_mti); + return mti; +} + +void Call::processMediaUpdate(OnCallMediaStateParam &prm) +{ + pjsua_call_info pj_ci; + unsigned mi; + + if (pjsua_call_get_info(id, &pj_ci) == PJ_SUCCESS) { + for (mi = 0; mi < pj_ci.media_cnt; mi++) { + if (mi >= medias.size()) { + if (pj_ci.media[mi].type == PJMEDIA_TYPE_AUDIO) { + medias.push_back(new CallAudioMedia); + } else { + medias.push_back(NULL); + } + } + + if (pj_ci.media[mi].type == PJMEDIA_TYPE_AUDIO) { + CallAudioMedia *aud_med = (CallAudioMedia *)medias[mi]; + + aud_med->setPortId(pj_ci.media[mi].stream.aud.conf_slot); + /* Add media if the conference slot ID is valid. */ + if (pj_ci.media[mi].stream.aud.conf_slot != PJSUA_INVALID_ID) + { + Endpoint::instance().mediaAdd((AudioMedia &)*aud_med); + } else { + Endpoint::instance().mediaRemove((AudioMedia &)*aud_med); + } + } + } + } + + /* Call media state callback. */ + onCallMediaState(prm); +} + +void Call::processStateChange(OnCallStateParam &prm) +{ + pjsua_call_info pj_ci; + unsigned mi; + + if (pjsua_call_get_info(id, &pj_ci) == PJ_SUCCESS && + pj_ci.state == PJSIP_INV_STATE_DISCONNECTED) + { + /* Clear medias. */ + for (mi = 0; mi < medias.size(); mi++) { + if (medias[mi]) + delete medias[mi]; + } + medias.clear(); + } + + onCallState(prm); + /* If the state is DISCONNECTED, this call may have already been deleted + * by the application in the callback, so do not access it anymore here. + */ +} diff --git a/pjsip/src/pjsua2/endpoint.cpp b/pjsip/src/pjsua2/endpoint.cpp new file mode 100644 index 00000000..d3ba3719 --- /dev/null +++ b/pjsip/src/pjsua2/endpoint.cpp @@ -0,0 +1,1612 @@ +/* $Id$ */ +/* + * 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(); +} diff --git a/pjsip/src/pjsua2/json.cpp b/pjsip/src/pjsua2/json.cpp new file mode 100644 index 00000000..afa25989 --- /dev/null +++ b/pjsip/src/pjsua2/json.cpp @@ -0,0 +1,544 @@ +/* $Id$ */ +/* + * 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/json.hpp> +#include <pjlib-util/errno.h> +#include <pj/file_io.h> +#include "util.hpp" + +#define THIS_FILE "json.cpp" + +using namespace pj; +using namespace std; + +/* Json node operations */ +static bool jsonNode_hasUnread(const ContainerNode*); +static string jsonNode_unreadName(const ContainerNode*n) + throw(Error); +static float jsonNode_readNumber(const ContainerNode*, + const string&) + throw(Error); +static bool jsonNode_readBool(const ContainerNode*, + const string&) + throw(Error); +static string jsonNode_readString(const ContainerNode*, + const string&) + throw(Error); +static StringVector jsonNode_readStringVector(const ContainerNode*, + const string&) + throw(Error); +static ContainerNode jsonNode_readContainer(const ContainerNode*, + const string &) + throw(Error); +static ContainerNode jsonNode_readArray(const ContainerNode*, + const string &) + throw(Error); +static void jsonNode_writeNumber(ContainerNode*, + const string &name, + float num) + throw(Error); +static void jsonNode_writeBool(ContainerNode*, + const string &name, + bool value) + throw(Error); +static void jsonNode_writeString(ContainerNode*, + const string &name, + const string &value) + throw(Error); +static void jsonNode_writeStringVector(ContainerNode*, + const string &name, + const StringVector &value) + throw(Error); +static ContainerNode jsonNode_writeNewContainer(ContainerNode*, + const string &name) + throw(Error); +static ContainerNode jsonNode_writeNewArray(ContainerNode*, + const string &name) + throw(Error); + +static container_node_op json_op = +{ + &jsonNode_hasUnread, + &jsonNode_unreadName, + &jsonNode_readNumber, + &jsonNode_readBool, + &jsonNode_readString, + &jsonNode_readStringVector, + &jsonNode_readContainer, + &jsonNode_readArray, + &jsonNode_writeNumber, + &jsonNode_writeBool, + &jsonNode_writeString, + &jsonNode_writeStringVector, + &jsonNode_writeNewContainer, + &jsonNode_writeNewArray +}; + +/////////////////////////////////////////////////////////////////////////////// +JsonDocument::JsonDocument() +: root(NULL) +{ + pj_caching_pool_init(&cp, NULL, 0); + pool = pj_pool_create(&cp.factory, "jsondoc", 512, 512, NULL); + if (!pool) + PJSUA2_RAISE_ERROR(PJ_ENOMEM); +} + +JsonDocument::~JsonDocument() +{ + if (pool) + pj_pool_release(pool); + pj_caching_pool_destroy(&cp); +} + +void JsonDocument::initRoot() const +{ + rootNode.op = &json_op; + rootNode.data.doc = (void*)this; + rootNode.data.data1 = (void*)root; + rootNode.data.data2 = root->value.children.next; +} + +void JsonDocument::loadFile(const string &filename) throw(Error) +{ + if (root) + PJSUA2_RAISE_ERROR3(PJ_EINVALIDOP, "JsonDocument.loadString()", + "Document already initialized"); + + if (!pj_file_exists(filename.c_str())) + PJSUA2_RAISE_ERROR(PJ_ENOTFOUND); + + pj_ssize_t size = (pj_ssize_t)pj_file_size(filename.c_str()); + pj_status_t status; + + char *buffer = (char*)pj_pool_alloc(pool, size+1); + pj_oshandle_t fd = 0; + unsigned parse_size; + char err_msg[120]; + pj_json_err_info err_info; + + err_msg[0] = '\0'; + + status = pj_file_open(pool, filename.c_str(), PJ_O_RDONLY, &fd); + if (status != PJ_SUCCESS) + goto on_error; + + status = pj_file_read(fd, buffer, &size); + if (status != PJ_SUCCESS) + goto on_error; + + pj_file_close(fd); + fd = NULL; + + if (size <= 0) { + status = PJ_EEOF; + goto on_error; + } + + parse_size = (unsigned)size; + root = pj_json_parse(pool, buffer, &parse_size, &err_info); + if (root == NULL) { + pj_ansi_snprintf(err_msg, sizeof(err_msg), + "JSON parsing failed: syntax error in file '%s' at " + "line %d column %d", + filename.c_str(), err_info.line, err_info.col); + PJ_LOG(1,(THIS_FILE, err_msg)); + status = PJLIB_UTIL_EINJSON; + goto on_error; + } + + initRoot(); + return; + +on_error: + if (fd) + pj_file_close(fd); + if (err_msg[0]) + PJSUA2_RAISE_ERROR3(status, "loadFile()", err_msg); + else + PJSUA2_RAISE_ERROR(status); +} + +void JsonDocument::loadString(const string &input) throw(Error) +{ + if (root) + PJSUA2_RAISE_ERROR3(PJ_EINVALIDOP, "JsonDocument.loadString()", + "Document already initialized"); + + unsigned size = input.size(); + char *buffer = (char*)pj_pool_alloc(pool, size+1); + unsigned parse_size = (unsigned)size; + pj_json_err_info err_info; + + pj_memcpy(buffer, input.c_str(), size); + + root = pj_json_parse(pool, buffer, &parse_size, &err_info); + if (root == NULL) { + char err_msg[80]; + + pj_ansi_snprintf(err_msg, sizeof(err_msg), + "JSON parsing failed at line %d column %d", + err_info.line, err_info.col); + PJ_LOG(1,(THIS_FILE, err_msg)); + PJSUA2_RAISE_ERROR3(PJLIB_UTIL_EINJSON, "loadString()", err_msg); + } + initRoot(); +} + +struct save_file_data +{ + pj_oshandle_t fd; +}; + +static pj_status_t json_file_writer(const char *s, + unsigned size, + void *user_data) +{ + save_file_data *sd = (save_file_data*)user_data; + pj_ssize_t ssize = (pj_ssize_t)size; + return pj_file_write(sd->fd, s, &ssize); +} + +void JsonDocument::saveFile(const string &filename) throw(Error) +{ + struct save_file_data sd; + pj_status_t status; + + /* Make sure root container has been created */ + getRootContainer(); + + status = pj_file_open(pool, filename.c_str(), PJ_O_WRONLY, &sd.fd); + if (status != PJ_SUCCESS) + PJSUA2_RAISE_ERROR(status); + + status = pj_json_writef(root, &json_file_writer, &sd.fd); + pj_file_close(sd.fd); + + if (status != PJ_SUCCESS) + PJSUA2_RAISE_ERROR(status); +} + +struct save_string_data +{ + string output; +}; + +static pj_status_t json_string_writer(const char *s, + unsigned size, + void *user_data) +{ + save_string_data *sd = (save_string_data*)user_data; + sd->output.append(s, size); + return PJ_SUCCESS; +} + +string JsonDocument::saveString() throw(Error) +{ + struct save_string_data sd; + pj_status_t status; + + /* Make sure root container has been created */ + getRootContainer(); + + status = pj_json_writef(root, &json_string_writer, &sd); + if (status != PJ_SUCCESS) + PJSUA2_RAISE_ERROR(status); + + return sd.output; +} + +ContainerNode & JsonDocument::getRootContainer() const +{ + if (!root) { + root = allocElement(); + pj_json_elem_obj(root, NULL); + initRoot(); + } + + return rootNode; +} + +pj_json_elem* JsonDocument::allocElement() const +{ + return (pj_json_elem*)pj_pool_alloc(pool, sizeof(pj_json_elem)); +} + +pj_pool_t *JsonDocument::getPool() +{ + return pool; +} + +/////////////////////////////////////////////////////////////////////////////// +struct json_node_data +{ + JsonDocument *doc; + pj_json_elem *jnode; + pj_json_elem *childPtr; +}; + +static bool jsonNode_hasUnread(const ContainerNode *node) +{ + json_node_data *jdat = (json_node_data*)&node->data; + return jdat->childPtr != (pj_json_elem*)&jdat->jnode->value.children; +} + +static void json_verify(struct json_node_data *jdat, + const char *op, + const string &name, + pj_json_val_type type) +{ + if (jdat->childPtr == (pj_json_elem*)&jdat->jnode->value.children) + PJSUA2_RAISE_ERROR3(PJ_EEOF, op, "No unread element"); + + /* If name is specified, then check if the names match, except + * when the node name itself is empty and the parent node is + * an array, then ignore the checking (JSON doesn't allow array + * elements to have name). + */ + if (name.size() && name.compare(0, name.size(), + jdat->childPtr->name.ptr, + jdat->childPtr->name.slen) && + jdat->childPtr->name.slen && + jdat->jnode->type != PJ_JSON_VAL_ARRAY) + { + char err_msg[80]; + pj_ansi_snprintf(err_msg, sizeof(err_msg), + "Name mismatch: expecting '%s' got '%.*s'", + name.c_str(), (int)jdat->childPtr->name.slen, + jdat->childPtr->name.ptr); + PJSUA2_RAISE_ERROR3(PJLIB_UTIL_EINJSON, op, err_msg); + } + + if (type != PJ_JSON_VAL_NULL && jdat->childPtr->type != type) { + char err_msg[80]; + pj_ansi_snprintf(err_msg, sizeof(err_msg), + "Type mismatch: expecting %d got %d", + type, jdat->childPtr->type); + PJSUA2_RAISE_ERROR3(PJLIB_UTIL_EINJSON, op, err_msg); + } +} + +static string jsonNode_unreadName(const ContainerNode *node) + throw(Error) +{ + json_node_data *jdat = (json_node_data*)&node->data; + json_verify(jdat, "unreadName()", "", PJ_JSON_VAL_NULL); + return pj2Str(jdat->childPtr->name); +} + +static float jsonNode_readNumber(const ContainerNode *node, + const string &name) + throw(Error) +{ + json_node_data *jdat = (json_node_data*)&node->data; + json_verify(jdat, "readNumber()", name, PJ_JSON_VAL_NUMBER); + jdat->childPtr = jdat->childPtr->next; + return jdat->childPtr->prev->value.num; +} + +static bool jsonNode_readBool(const ContainerNode *node, + const string &name) + throw(Error) +{ + json_node_data *jdat = (json_node_data*)&node->data; + json_verify(jdat, "readBool()", name, PJ_JSON_VAL_BOOL); + jdat->childPtr = jdat->childPtr->next; + return PJ2BOOL(jdat->childPtr->prev->value.is_true); +} + +static string jsonNode_readString(const ContainerNode *node, + const string &name) + throw(Error) +{ + json_node_data *jdat = (json_node_data*)&node->data; + json_verify(jdat, "readString()", name, PJ_JSON_VAL_STRING); + jdat->childPtr = jdat->childPtr->next; + return pj2Str(jdat->childPtr->prev->value.str); +} + +static StringVector jsonNode_readStringVector(const ContainerNode *node, + const string &name) + throw(Error) +{ + json_node_data *jdat = (json_node_data*)&node->data; + json_verify(jdat, "readStringVector()", name, PJ_JSON_VAL_ARRAY); + + StringVector result; + pj_json_elem *child = jdat->childPtr->value.children.next; + while (child != (pj_json_elem*)&jdat->childPtr->value.children) { + if (child->type != PJ_JSON_VAL_STRING) { + char err_msg[80]; + pj_ansi_snprintf(err_msg, sizeof(err_msg), + "Elements not string but type %d", + child->type); + PJSUA2_RAISE_ERROR3(PJLIB_UTIL_EINJSON, "readStringVector()", + err_msg); + } + result.push_back(pj2Str(child->value.str)); + child = child->next; + } + + jdat->childPtr = jdat->childPtr->next; + return result; +} + +static ContainerNode jsonNode_readContainer(const ContainerNode *node, + const string &name) + throw(Error) +{ + json_node_data *jdat = (json_node_data*)&node->data; + json_verify(jdat, "readContainer()", name, PJ_JSON_VAL_OBJ); + + ContainerNode json_node; + + json_node.op = &json_op; + json_node.data.doc = (void*)jdat->doc; + json_node.data.data1 = (void*)jdat->childPtr; + json_node.data.data2 = (void*)jdat->childPtr->value.children.next; + + jdat->childPtr = jdat->childPtr->next; + return json_node; +} + +static ContainerNode jsonNode_readArray(const ContainerNode *node, + const string &name) + throw(Error) +{ + json_node_data *jdat = (json_node_data*)&node->data; + json_verify(jdat, "readArray()", name, PJ_JSON_VAL_ARRAY); + + ContainerNode json_node; + + json_node.op = &json_op; + json_node.data.doc = (void*)jdat->doc; + json_node.data.data1 = (void*)jdat->childPtr; + json_node.data.data2 = (void*)jdat->childPtr->value.children.next; + + jdat->childPtr = jdat->childPtr->next; + return json_node; +} + +static pj_str_t alloc_name(JsonDocument *doc, const string &name) +{ + pj_str_t new_name; + pj_strdup2(doc->getPool(), &new_name, name.c_str()); + return new_name; +} + +static void jsonNode_writeNumber(ContainerNode *node, + const string &name, + float num) + throw(Error) +{ + json_node_data *jdat = (json_node_data*)&node->data; + pj_json_elem *el = jdat->doc->allocElement(); + pj_str_t nm = alloc_name(jdat->doc, name); + pj_json_elem_number(el, &nm, num); + pj_json_elem_add(jdat->jnode, el); +} + +static void jsonNode_writeBool(ContainerNode *node, + const string &name, + bool value) + throw(Error) +{ + json_node_data *jdat = (json_node_data*)&node->data; + pj_json_elem *el = jdat->doc->allocElement(); + pj_str_t nm = alloc_name(jdat->doc, name); + pj_json_elem_bool(el, &nm, value); + pj_json_elem_add(jdat->jnode, el); +} + +static void jsonNode_writeString(ContainerNode *node, + const string &name, + const string &value) + throw(Error) +{ + json_node_data *jdat = (json_node_data*)&node->data; + pj_json_elem *el = jdat->doc->allocElement(); + pj_str_t nm = alloc_name(jdat->doc, name); + pj_str_t new_val; + pj_strdup2(jdat->doc->getPool(), &new_val, value.c_str()); + pj_json_elem_string(el, &nm, &new_val); + + pj_json_elem_add(jdat->jnode, el); +} + +static void jsonNode_writeStringVector(ContainerNode *node, + const string &name, + const StringVector &value) + throw(Error) +{ + json_node_data *jdat = (json_node_data*)&node->data; + pj_json_elem *el = jdat->doc->allocElement(); + pj_str_t nm = alloc_name(jdat->doc, name); + + pj_json_elem_array(el, &nm); + for (unsigned i=0; i<value.size(); ++i) { + pj_str_t new_val; + + pj_strdup2(jdat->doc->getPool(), &new_val, value[i].c_str()); + pj_json_elem *child = jdat->doc->allocElement(); + pj_json_elem_string(child, NULL, &new_val); + pj_json_elem_add(el, child); + } + + pj_json_elem_add(jdat->jnode, el); +} + +static ContainerNode jsonNode_writeNewContainer(ContainerNode *node, + const string &name) + throw(Error) +{ + json_node_data *jdat = (json_node_data*)&node->data; + pj_json_elem *el = jdat->doc->allocElement(); + pj_str_t nm = alloc_name(jdat->doc, name); + + pj_json_elem_obj(el, &nm); + pj_json_elem_add(jdat->jnode, el); + + ContainerNode json_node; + + json_node.op = &json_op; + json_node.data.doc = (void*)jdat->doc; + json_node.data.data1 = (void*)el; + json_node.data.data2 = (void*)el->value.children.next; + + return json_node; +} + +static ContainerNode jsonNode_writeNewArray(ContainerNode *node, + const string &name) + throw(Error) +{ + json_node_data *jdat = (json_node_data*)&node->data; + pj_json_elem *el = jdat->doc->allocElement(); + pj_str_t nm = alloc_name(jdat->doc, name); + + pj_json_elem_array(el, &nm); + pj_json_elem_add(jdat->jnode, el); + + ContainerNode json_node; + + json_node.op = &json_op; + json_node.data.doc = (void*)jdat->doc; + json_node.data.data1 = (void*)el; + json_node.data.data2 = (void*)el->value.children.next; + + return json_node; +} diff --git a/pjsip/src/pjsua2/media.cpp b/pjsip/src/pjsua2/media.cpp new file mode 100644 index 00000000..175d5987 --- /dev/null +++ b/pjsip/src/pjsua2/media.cpp @@ -0,0 +1,779 @@ +/* $Id$ */ +/* + * 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 <pj/ctype.h> +#include <pjsua2/media.hpp> +#include <pjsua2/types.hpp> +#include <pjsua2/endpoint.hpp> +#include "util.hpp" + +using namespace pj; +using namespace std; + +#define THIS_FILE "media.cpp" +#define MAX_FILE_NAMES 64 +#define MAX_DEV_COUNT 64 + +/////////////////////////////////////////////////////////////////////////////// +void MediaFormatAudio::fromPj(const pjmedia_format &format) +{ + if ((format.type != PJMEDIA_TYPE_AUDIO) && + (format.detail_type != PJMEDIA_FORMAT_DETAIL_AUDIO)) + { + type = PJMEDIA_TYPE_UNKNOWN; + return; + } + + id = format.id; + type = format.type; + + /* Detail. */ + clockRate = format.det.aud.clock_rate; + channelCount = format.det.aud.channel_count; + frameTimeUsec = format.det.aud.frame_time_usec; + bitsPerSample = format.det.aud.bits_per_sample; + avgBps = format.det.aud.avg_bps; + maxBps = format.det.aud.max_bps; +} + +pjmedia_format MediaFormatAudio::toPj() const +{ + pjmedia_format pj_format; + + pj_format.id = id; + pj_format.type = type; + + pj_format.detail_type = PJMEDIA_FORMAT_DETAIL_AUDIO; + pj_format.det.aud.clock_rate = clockRate; + pj_format.det.aud.channel_count = channelCount; + pj_format.det.aud.frame_time_usec = frameTimeUsec; + pj_format.det.aud.bits_per_sample = bitsPerSample; + pj_format.det.aud.avg_bps = avgBps; + pj_format.det.aud.max_bps = maxBps; + + return pj_format; +} + +/////////////////////////////////////////////////////////////////////////////// +/* Audio Media operations. */ +void ConfPortInfo::fromPj(const pjsua_conf_port_info &port_info) +{ + portId = port_info.slot_id; + name = pj2Str(port_info.name); + format.fromPj(port_info.format); + txLevelAdj = port_info.tx_level_adj; + rxLevelAdj = port_info.rx_level_adj; + + /* + format.id = PJMEDIA_FORMAT_PCM; + format.type = PJMEDIA_TYPE_AUDIO; + format.clockRate = port_info.clock_rate; + format.channelCount = port_info.channel_count; + format.bitsPerSample = port_info.bits_per_sample; + format.frameTimeUsec = (port_info.samples_per_frame * + 1000000) / + (port_info.clock_rate * + port_info.channel_count); + + format.avgBps = format.maxBps = port_info.clock_rate * + port_info.channel_count * + port_info.bits_per_sample; + */ + listeners.clear(); + for (unsigned i=0; i<port_info.listener_cnt; ++i) { + listeners.push_back(port_info.listeners[i]); + } +} +/////////////////////////////////////////////////////////////////////////////// +Media::Media(pjmedia_type med_type) +: type(med_type) +{ + +} + +Media::~Media() +{ + +} + +pjmedia_type Media::getType() const +{ + return type; +} + +/////////////////////////////////////////////////////////////////////////////// +AudioMedia::AudioMedia() +: Media(PJMEDIA_TYPE_AUDIO), id(PJSUA_INVALID_ID), mediaPool(NULL) +{ + +} + +void AudioMedia::registerMediaPort(MediaPort port) throw(Error) +{ + /* Check if media already added to Conf bridge. */ + pj_assert(!Endpoint::instance().mediaExists(*this)); + + if (port != NULL) { + pj_assert(id == PJSUA_INVALID_ID); + + pj_caching_pool_init(&mediaCachingPool, NULL, 0); + + mediaPool = pj_pool_create(&mediaCachingPool.factory, + "media", + 512, + 512, + NULL); + + if (!mediaPool) { + pj_caching_pool_destroy(&mediaCachingPool); + PJSUA2_RAISE_ERROR(PJ_ENOMEM); + } + + PJSUA2_CHECK_EXPR( pjsua_conf_add_port(mediaPool, + (pjmedia_port *)port, + &id) ); + } + + Endpoint::instance().mediaAdd(*this); +} + +void AudioMedia::unregisterMediaPort() +{ + if (id != PJSUA_INVALID_ID) { + pjsua_conf_remove_port(id); + id = PJSUA_INVALID_ID; + } + + if (mediaPool) { + pj_pool_release(mediaPool); + mediaPool = NULL; + pj_caching_pool_destroy(&mediaCachingPool); + } + + Endpoint::instance().mediaRemove(*this); +} + +AudioMedia::~AudioMedia() +{ + unregisterMediaPort(); +} + +ConfPortInfo AudioMedia::getPortInfo() const throw(Error) +{ + return AudioMedia::getPortInfoFromId(id); +} + +int AudioMedia::getPortId() const +{ + return id; +} + +ConfPortInfo AudioMedia::getPortInfoFromId(int port_id) throw(Error) +{ + pjsua_conf_port_info pj_info; + ConfPortInfo pi; + + PJSUA2_CHECK_EXPR( pjsua_conf_get_port_info(port_id, &pj_info) ); + pi.fromPj(pj_info); + return pi; +} + +void AudioMedia::startTransmit(const AudioMedia &sink) const throw(Error) +{ + PJSUA2_CHECK_EXPR( pjsua_conf_connect(id, sink.id) ); +} + +void AudioMedia::stopTransmit(const AudioMedia &sink) const throw(Error) +{ + PJSUA2_CHECK_EXPR( pjsua_conf_disconnect(id, sink.id) ); +} + +void AudioMedia::adjustRxLevel(float level) throw(Error) +{ + PJSUA2_CHECK_EXPR( pjsua_conf_adjust_rx_level(id, level) ); +} + +void AudioMedia::adjustTxLevel(float level) throw(Error) +{ + PJSUA2_CHECK_EXPR( pjsua_conf_adjust_tx_level(id, level) ); +} + +unsigned AudioMedia::getRxLevel() const throw(Error) +{ + return getSignalLevel(true); +} + +unsigned AudioMedia::getTxLevel() const throw(Error) +{ + return getSignalLevel(false); +} + +unsigned AudioMedia::getSignalLevel(bool is_rx) const throw(Error) +{ + unsigned rx_level; + unsigned tx_level; + + PJSUA2_CHECK_EXPR( pjsua_conf_get_signal_level(id, &tx_level, &rx_level) ); + return is_rx?rx_level:tx_level; +} + +AudioMedia* AudioMedia::typecastFromMedia(Media *media) +{ + return static_cast<AudioMedia*>(media); +} + +/////////////////////////////////////////////////////////////////////////////// + +AudioMediaPlayer::AudioMediaPlayer() +: playerId(PJSUA_INVALID_ID) +{ + +} + +AudioMediaPlayer::~AudioMediaPlayer() +{ + if (playerId != PJSUA_INVALID_ID) + pjsua_player_destroy(playerId); +} + +void AudioMediaPlayer::createPlayer(const string &file_name, + unsigned options) + throw(Error) +{ + pj_str_t pj_name = str2Pj(file_name); + + PJSUA2_CHECK_EXPR( pjsua_player_create(&pj_name, + options, + &playerId) ); + + /* Get media port id. */ + id = pjsua_player_get_conf_port(playerId); + + registerMediaPort(NULL); +} + +void AudioMediaPlayer::createPlaylist(const StringVector &file_names, + const string &label, + unsigned options) + throw(Error) +{ + pj_str_t pj_files[MAX_FILE_NAMES]; + unsigned i, count = 0; + pj_str_t pj_lbl = str2Pj(label); + + count = PJ_ARRAY_SIZE(pj_files); + + for(i=0; i<file_names.size() && i<count;++i) + { + const string &file_name = file_names[i]; + + pj_files[i] = str2Pj(file_name); + } + + PJSUA2_CHECK_EXPR( pjsua_playlist_create(pj_files, + i, + &pj_lbl, + options, + &playerId) ); + + /* Get media port id. */ + id = pjsua_player_get_conf_port(playerId); + + registerMediaPort(NULL); +} + +void AudioMediaPlayer::setPos(pj_uint32_t samples) throw(Error) +{ + PJSUA2_CHECK_EXPR( pjsua_player_set_pos(playerId, samples) ); +} + +AudioMediaPlayer* AudioMediaPlayer::typecastFromAudioMedia( + AudioMedia *media) +{ + return static_cast<AudioMediaPlayer*>(media); +} + +/////////////////////////////////////////////////////////////////////////////// +AudioMediaRecorder::AudioMediaRecorder() +: recorderId(PJSUA_INVALID_ID) +{ + +} + +AudioMediaRecorder::~AudioMediaRecorder() +{ + if (recorderId != PJSUA_INVALID_ID) + pjsua_recorder_destroy(recorderId); +} + +void AudioMediaRecorder::createRecorder(const string &file_name, + unsigned enc_type, + pj_ssize_t max_size, + unsigned options) + throw(Error) +{ + PJ_UNUSED_ARG(max_size); + + pj_str_t pj_name = str2Pj(file_name); + + PJSUA2_CHECK_EXPR( pjsua_recorder_create(&pj_name, + enc_type, + NULL, + -1, + options, + &recorderId) ); + + /* Get media port id. */ + id = pjsua_recorder_get_conf_port(recorderId); + + registerMediaPort(NULL); +} + +AudioMediaRecorder* AudioMediaRecorder::typecastFromAudioMedia( + AudioMedia *media) +{ + return static_cast<AudioMediaRecorder*>(media); +} + +/////////////////////////////////////////////////////////////////////////////// +void AudioDevInfo::fromPj(const pjmedia_aud_dev_info &dev_info) +{ + name = dev_info.name; + inputCount = dev_info.input_count; + outputCount = dev_info.output_count; + defaultSamplesPerSec = dev_info.default_samples_per_sec; + driver = dev_info.driver; + caps = dev_info.caps; + routes = dev_info.routes; + + for (unsigned i=0; i<dev_info.ext_fmt_cnt;++i) { + MediaFormatAudio *format = new MediaFormatAudio; + + format->fromPj(dev_info.ext_fmt[i]); + if (format->type == PJMEDIA_TYPE_AUDIO) + extFmt.push_back(format); + } +} + +AudioDevInfo::~AudioDevInfo() +{ + for(unsigned i=0;i<extFmt.size();++i) { + delete extFmt[i]; + } + extFmt.clear(); +} + +/////////////////////////////////////////////////////////////////////////////// +/* + * Simple AudioMedia class for sound device. + */ +class DevAudioMedia : public AudioMedia +{ +public: + DevAudioMedia(); + ~DevAudioMedia(); +}; + +DevAudioMedia::DevAudioMedia() +{ + this->id = 0; + registerMediaPort(NULL); +} + +DevAudioMedia::~DevAudioMedia() +{ + /* Avoid removing this port (conf port id=0) from conference */ + this->id = PJSUA_INVALID_ID; +} + +/////////////////////////////////////////////////////////////////////////////// +/* Audio device operations. */ + +AudDevManager::AudDevManager() +: devMedia(NULL) +{ +} + +AudDevManager::~AudDevManager() +{ + // At this point, devMedia should have been cleaned up by Endpoint, + // as AudDevManager destructor is called after Endpoint destructor. + //delete devMedia; + + clearAudioDevList(); +} + +int AudDevManager::getCaptureDev() const throw(Error) +{ + return getActiveDev(true); +} + +AudioMedia &AudDevManager::getCaptureDevMedia() throw(Error) +{ + if (!devMedia) + devMedia = new DevAudioMedia; + return *devMedia; +} + +int AudDevManager::getPlaybackDev() const throw(Error) +{ + return getActiveDev(false); +} + +AudioMedia &AudDevManager::getPlaybackDevMedia() throw(Error) +{ + if (!devMedia) + devMedia = new DevAudioMedia; + return *devMedia; +} + +void AudDevManager::setCaptureDev(int capture_dev) const throw(Error) +{ + int playback_dev = getPlaybackDev(); + + PJSUA2_CHECK_EXPR( pjsua_set_snd_dev(capture_dev, playback_dev) ); +} + +void AudDevManager::setPlaybackDev(int playback_dev) const throw(Error) +{ + int capture_dev = getCaptureDev(); + + PJSUA2_CHECK_EXPR( pjsua_set_snd_dev(capture_dev, playback_dev) ); +} + +const AudioDevInfoVector &AudDevManager::enumDev() throw(Error) +{ + pjmedia_aud_dev_info pj_info[MAX_DEV_COUNT]; + unsigned count; + + PJSUA2_CHECK_EXPR( pjsua_enum_aud_devs(pj_info, &count) ); + + pj_enter_critical_section(); + clearAudioDevList(); + for (unsigned i = 0; (i<count && i<MAX_DEV_COUNT) ;++i) { + AudioDevInfo *dev_info = new AudioDevInfo; + dev_info->fromPj(pj_info[i]); + audioDevList.push_back(dev_info); + } + pj_leave_critical_section(); + return audioDevList; +} + +void AudDevManager::setNullDev() throw(Error) +{ + PJSUA2_CHECK_EXPR( pjsua_set_null_snd_dev() ); +} + +MediaPort *AudDevManager::setNoDev() +{ + return (MediaPort*)pjsua_set_no_snd_dev(); +} + +void AudDevManager::setEcOptions(unsigned tail_msec, + unsigned options) throw(Error) +{ + PJSUA2_CHECK_EXPR( pjsua_set_ec(tail_msec, options) ); +} + +unsigned AudDevManager::getEcTail() const throw(Error) +{ + unsigned tail_msec = 0; + + PJSUA2_CHECK_EXPR( pjsua_get_ec_tail(&tail_msec) ); + + return tail_msec; +} + +bool AudDevManager::sndIsActive() const +{ + return PJ2BOOL(pjsua_snd_is_active()); +} + +void AudDevManager::refreshDevs() throw(Error) +{ + PJSUA2_CHECK_EXPR( pjmedia_aud_dev_refresh() ); +} + +unsigned AudDevManager::getDevCount() const +{ + return pjmedia_aud_dev_count(); +} + +AudioDevInfo +AudDevManager::getDevInfo(int id) const throw(Error) +{ + AudioDevInfo dev_info; + pjmedia_aud_dev_info pj_info; + + PJSUA2_CHECK_EXPR( pjmedia_aud_dev_get_info(id, &pj_info) ); + + dev_info.fromPj(pj_info); + return dev_info; +} + +int AudDevManager::lookupDev(const string &drv_name, + const string &dev_name) const throw(Error) +{ + pjmedia_aud_dev_index pj_idx = 0; + + PJSUA2_CHECK_EXPR( pjmedia_aud_dev_lookup(drv_name.c_str(), + dev_name.c_str(), + &pj_idx) ); + + return pj_idx; +} + + +string AudDevManager::capName(pjmedia_aud_dev_cap cap) const +{ + return pjmedia_aud_dev_cap_name(cap, NULL); +} + +void +AudDevManager::setExtFormat(const MediaFormatAudio &format, + bool keep) throw(Error) +{ + pjmedia_format pj_format = format.toPj(); + + PJSUA2_CHECK_EXPR( pjsua_snd_set_setting(PJMEDIA_AUD_DEV_CAP_EXT_FORMAT, + &pj_format, + keep) ); +} + +MediaFormatAudio AudDevManager::getExtFormat() const throw(Error) +{ + pjmedia_format pj_format; + MediaFormatAudio format; + + PJSUA2_CHECK_EXPR( pjsua_snd_get_setting(PJMEDIA_AUD_DEV_CAP_EXT_FORMAT, + &pj_format) ); + + format.fromPj(pj_format); + + return format; +} + +void AudDevManager::setInputLatency(unsigned latency_msec, + bool keep) throw(Error) +{ + PJSUA2_CHECK_EXPR( pjsua_snd_set_setting(PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY, + &latency_msec, + keep) ); +} + +unsigned AudDevManager::getInputLatency() const throw(Error) +{ + unsigned latency_msec = 0; + + PJSUA2_CHECK_EXPR( pjsua_snd_get_setting(PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY, + &latency_msec) ); + + return latency_msec; +} + +void +AudDevManager::setOutputLatency(unsigned latency_msec, + bool keep) throw(Error) +{ + PJSUA2_CHECK_EXPR( pjsua_snd_set_setting(PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY, + &latency_msec, + keep) ); +} + +unsigned AudDevManager::getOutputLatency() const throw(Error) +{ + unsigned latency_msec = 0; + + PJSUA2_CHECK_EXPR( pjsua_snd_get_setting(PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY, + &latency_msec) ); + + return latency_msec; +} + +void AudDevManager::setInputVolume(unsigned volume, bool keep) throw(Error) +{ + PJSUA2_CHECK_EXPR( + pjsua_snd_set_setting(PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING, + &volume, + keep) ); +} + +unsigned AudDevManager::getInputVolume() const throw(Error) +{ + unsigned volume = 0; + + PJSUA2_CHECK_EXPR( + pjsua_snd_get_setting(PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING, + &volume) ); + + return volume; +} + +void AudDevManager::setOutputVolume(unsigned volume, bool keep) throw(Error) +{ + PJSUA2_CHECK_EXPR( + pjsua_snd_set_setting(PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING, + &volume, + keep) ); +} + +unsigned AudDevManager::getOutputVolume() const throw(Error) +{ + unsigned volume = 0; + + PJSUA2_CHECK_EXPR( + pjsua_snd_get_setting(PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING, + &volume) ); + + return volume; +} + +unsigned AudDevManager::getInputSignal() const throw(Error) +{ + unsigned signal = 0; + + PJSUA2_CHECK_EXPR( + pjsua_snd_get_setting(PJMEDIA_AUD_DEV_CAP_INPUT_SIGNAL_METER, + &signal) ); + + return signal; +} + +unsigned AudDevManager::getOutputSignal() const throw(Error) +{ + unsigned signal = 0; + + PJSUA2_CHECK_EXPR( + pjsua_snd_get_setting(PJMEDIA_AUD_DEV_CAP_OUTPUT_SIGNAL_METER, + &signal) ); + + return signal; +} + +void +AudDevManager::setInputRoute(pjmedia_aud_dev_route route, + bool keep) throw(Error) +{ + PJSUA2_CHECK_EXPR( pjsua_snd_set_setting(PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE, + &route, + keep) ); +} + +pjmedia_aud_dev_route AudDevManager::getInputRoute() const throw(Error) +{ + pjmedia_aud_dev_route route = PJMEDIA_AUD_DEV_ROUTE_DEFAULT; + + PJSUA2_CHECK_EXPR( pjsua_snd_get_setting(PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE, + &route) ); + + return route; +} + +void +AudDevManager::setOutputRoute(pjmedia_aud_dev_route route, + bool keep) throw(Error) +{ + PJSUA2_CHECK_EXPR( pjsua_snd_set_setting(PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE, + &route, + keep) ); +} + +pjmedia_aud_dev_route AudDevManager::getOutputRoute() const throw(Error) +{ + pjmedia_aud_dev_route route = PJMEDIA_AUD_DEV_ROUTE_DEFAULT; + + PJSUA2_CHECK_EXPR( pjsua_snd_get_setting(PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE, + &route) ); + + return route; +} + +void AudDevManager::setVad(bool enable, bool keep) throw(Error) +{ + PJSUA2_CHECK_EXPR( pjsua_snd_set_setting(PJMEDIA_AUD_DEV_CAP_VAD, + &enable, + keep) ); +} + +bool AudDevManager::getVad() const throw(Error) +{ + bool enable = false; + + PJSUA2_CHECK_EXPR( pjsua_snd_get_setting(PJMEDIA_AUD_DEV_CAP_VAD, + &enable) ); + + return enable; +} + +void AudDevManager::setCng(bool enable, bool keep) throw(Error) +{ + PJSUA2_CHECK_EXPR( pjsua_snd_set_setting(PJMEDIA_AUD_DEV_CAP_CNG, + &enable, + keep) ); +} + +bool AudDevManager::getCng() const throw(Error) +{ + bool enable = false; + + PJSUA2_CHECK_EXPR( pjsua_snd_get_setting(PJMEDIA_AUD_DEV_CAP_CNG, + &enable) ); + + return enable; +} + +void AudDevManager::setPlc(bool enable, bool keep) throw(Error) +{ + PJSUA2_CHECK_EXPR( pjsua_snd_set_setting(PJMEDIA_AUD_DEV_CAP_PLC, + &enable, + keep) ); +} + +bool AudDevManager::getPlc() const throw(Error) +{ + bool enable = false; + + PJSUA2_CHECK_EXPR( pjsua_snd_get_setting(PJMEDIA_AUD_DEV_CAP_PLC, + &enable) ); + + return enable; +} + +void AudDevManager::clearAudioDevList() +{ + for(unsigned i=0;i<audioDevList.size();++i) { + delete audioDevList[i]; + } + audioDevList.clear(); +} + +int AudDevManager::getActiveDev(bool is_capture) const throw(Error) +{ + int capture_dev = 0, playback_dev = 0; + PJSUA2_CHECK_EXPR( pjsua_get_snd_dev(&capture_dev, &playback_dev) ); + + return is_capture?capture_dev:playback_dev; +} + +/////////////////////////////////////////////////////////////////////////////// +void CodecInfo::fromPj(const pjsua_codec_info &codec_info) +{ + codecId = pj2Str(codec_info.codec_id); + priority = codec_info.priority; + desc = pj2Str(codec_info.desc); +} diff --git a/pjsip/src/pjsua2/persistent.cpp b/pjsip/src/pjsua2/persistent.cpp new file mode 100644 index 00000000..cd94316c --- /dev/null +++ b/pjsip/src/pjsua2/persistent.cpp @@ -0,0 +1,227 @@ +/* $Id$ */ +/* + * 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/persistent.hpp> + +using namespace pj; +using namespace std; + + +bool PersistentDocument::hasUnread() const +{ + return getRootContainer().hasUnread(); +} + +string PersistentDocument::unreadName() const throw(Error) +{ + return getRootContainer().unreadName(); +} + +int PersistentDocument::readInt(const string &name) const throw(Error) +{ + return (int)getRootContainer().readNumber(name); +} + +float PersistentDocument::readNumber(const string &name) const throw(Error) +{ + return getRootContainer().readNumber(name); +} + +bool PersistentDocument::readBool(const string &name) const throw(Error) +{ + return getRootContainer().readBool(name); +} + +string PersistentDocument::readString(const string &name) const throw(Error) +{ + return getRootContainer().readString(name); +} + +StringVector PersistentDocument::readStringVector(const string &name) const + throw(Error) +{ + return getRootContainer().readStringVector(name); +} + +void PersistentDocument::readObject(PersistentObject &obj) const throw(Error) +{ + getRootContainer().readObject(obj); +} + +ContainerNode PersistentDocument::readContainer(const string &name) const + throw(Error) +{ + return getRootContainer().readContainer(name); +} + +ContainerNode PersistentDocument::readArray(const string &name) const + throw(Error) +{ + return getRootContainer().readArray(name); +} + +void PersistentDocument::writeNumber(const string &name, + float num) throw(Error) +{ + getRootContainer().writeNumber(name, num); +} + +void PersistentDocument::writeInt(const string &name, + int num) throw(Error) +{ + getRootContainer().writeNumber(name, (float)num); +} + +void PersistentDocument::writeBool(const string &name, + bool value) throw(Error) +{ + getRootContainer().writeBool(name, value); +} + +void PersistentDocument::writeString(const string &name, + const string &value) throw(Error) +{ + getRootContainer().writeString(name, value); +} + +void PersistentDocument::writeStringVector(const string &name, + const StringVector &value) + throw(Error) +{ + getRootContainer().writeStringVector(name, value); +} + +void PersistentDocument::writeObject(const PersistentObject &obj) throw(Error) +{ + getRootContainer().writeObject(obj); +} + +ContainerNode PersistentDocument::writeNewContainer(const string &name) + throw(Error) +{ + return getRootContainer().writeNewContainer(name); +} + +ContainerNode PersistentDocument::writeNewArray(const string &name) + throw(Error) +{ + return getRootContainer().writeNewArray(name); +} + +/////////////////////////////////////////////////////////////////////////////// + +bool ContainerNode::hasUnread() const +{ + return op->hasUnread(this); +} + +string ContainerNode::unreadName() const throw(Error) +{ + return op->unreadName(this); +} + +int ContainerNode::readInt(const string &name) const throw(Error) +{ + return (int)op->readNumber(this, name); +} + +float ContainerNode::readNumber(const string &name) const throw(Error) +{ + return op->readNumber(this, name); +} + +bool ContainerNode::readBool(const string &name) const throw(Error) +{ + return op->readBool(this, name); +} + +string ContainerNode::readString(const string &name) const throw(Error) +{ + return op->readString(this, name); +} + +StringVector ContainerNode::readStringVector(const string &name) const + throw(Error) +{ + return op->readStringVector(this, name); +} + +void ContainerNode::readObject(PersistentObject &obj) const throw(Error) +{ + obj.readObject(*this); +} + +ContainerNode ContainerNode::readContainer(const string &name) const + throw(Error) +{ + return op->readContainer(this, name); +} + +ContainerNode ContainerNode::readArray(const string &name) const + throw(Error) +{ + return op->readArray(this, name); +} + +void ContainerNode::writeNumber(const string &name, + float num) throw(Error) +{ + return op->writeNumber(this, name, num); +} + +void ContainerNode::writeInt(const string &name, + int num) throw(Error) +{ + return op->writeNumber(this, name, (float)num); +} + +void ContainerNode::writeBool(const string &name, + bool value) throw(Error) +{ + return op->writeBool(this, name, value); +} + +void ContainerNode::writeString(const string &name, + const string &value) throw(Error) +{ + return op->writeString(this, name, value); +} + +void ContainerNode::writeStringVector(const string &name, + const StringVector &value) + throw(Error) +{ + return op->writeStringVector(this, name, value); +} + +void ContainerNode::writeObject(const PersistentObject &obj) throw(Error) +{ + obj.writeObject(*this); +} + +ContainerNode ContainerNode::writeNewContainer(const string &name) + throw(Error) +{ + return op->writeNewContainer(this, name); +} + +ContainerNode ContainerNode::writeNewArray(const string &name) + throw(Error) +{ + return op->writeNewArray(this, name); +} diff --git a/pjsip/src/pjsua2/presence.cpp b/pjsip/src/pjsua2/presence.cpp new file mode 100644 index 00000000..205a85ee --- /dev/null +++ b/pjsip/src/pjsua2/presence.cpp @@ -0,0 +1,190 @@ +/* $Id$ */ +/* + * 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/presence.hpp> +#include <pjsua2/account.hpp> +#include "util.hpp" + +using namespace pj; +using namespace std; + +#define THIS_FILE "presence.cpp" + + +/////////////////////////////////////////////////////////////////////////////// + +PresenceStatus::PresenceStatus() +: status(PJSUA_BUDDY_STATUS_UNKNOWN), activity(PJRPID_ACTIVITY_UNKNOWN) +{ +} + + +/////////////////////////////////////////////////////////////////////////////// + +void BuddyConfig::readObject(const ContainerNode &node) throw(Error) +{ + ContainerNode this_node = node.readContainer("BuddyConfig"); + + NODE_READ_STRING ( this_node, uri); + NODE_READ_BOOL ( this_node, subscribe); +} + +void BuddyConfig::writeObject(ContainerNode &node) const throw(Error) +{ + ContainerNode this_node = node.writeNewContainer("BuddyConfig"); + + NODE_WRITE_STRING ( this_node, uri); + NODE_WRITE_BOOL ( this_node, subscribe); +} + +////////////////////////////////////////////////////////////////////////////// + +void BuddyInfo::fromPj(const pjsua_buddy_info &pbi) +{ + uri = pj2Str(pbi.uri); + contact = pj2Str(pbi.contact); + presMonitorEnabled = PJ2BOOL(pbi.monitor_pres); + subState = pbi.sub_state; + subStateName = string(pbi.sub_state_name); + subTermCode = (pjsip_status_code)pbi.sub_term_code; + subTermReason = pj2Str(pbi.sub_term_reason); + + /* Presence status */ + presStatus.status = pbi.status; + presStatus.statusText = pj2Str(pbi.status_text); + presStatus.activity = pbi.rpid.activity; + presStatus.note = pj2Str(pbi.rpid.note); + presStatus.rpidId = pj2Str(pbi.rpid.id); +} + +////////////////////////////////////////////////////////////////////////////// + +/* + * Constructor. + */ +Buddy::Buddy() +: id(PJSUA_INVALID_ID) +{ +} + +/* + * Destructor. + */ +Buddy::~Buddy() +{ + if (isValid()) { + pjsua_buddy_set_user_data(id, NULL); + pjsua_buddy_del(id); + + /* Remove from account buddy list */ + acc->removeBuddy(this); + } +} + +/* + * Create buddy and register the buddy to PJSUA-LIB. + */ +void Buddy::create(Account &account, const BuddyConfig &cfg) throw(Error) +{ + pjsua_buddy_config pj_cfg; + pjsua_buddy_config_default(&pj_cfg); + + if (!account.isValid()) + PJSUA2_RAISE_ERROR3(PJ_EINVAL, "Buddy::create()", "Invalid account"); + + pj_cfg.uri = str2Pj(cfg.uri); + pj_cfg.subscribe = cfg.subscribe; + pj_cfg.user_data = (void*)this; + PJSUA2_CHECK_EXPR( pjsua_buddy_add(&pj_cfg, &id) ); + + acc = &account; + acc->addBuddy(this); +} + +/* + * Check if this buddy is valid. + */ +bool Buddy::isValid() const +{ + return PJ2BOOL( pjsua_buddy_is_valid(id) ); +} + +/* + * Get detailed buddy info. + */ +BuddyInfo Buddy::getInfo() const throw(Error) +{ + pjsua_buddy_info pj_bi; + BuddyInfo bi; + + PJSUA2_CHECK_EXPR( pjsua_buddy_get_info(id, &pj_bi) ); + bi.fromPj(pj_bi); + return bi; +} + +/* + * Enable/disable buddy's presence monitoring. + */ +void Buddy::subscribePresence(bool subscribe) throw(Error) +{ + PJSUA2_CHECK_EXPR( pjsua_buddy_subscribe_pres(id, subscribe) ); +} + + +/* + * Update the presence information for the buddy. + */ +void Buddy::updatePresence(void) throw(Error) +{ + PJSUA2_CHECK_EXPR( pjsua_buddy_update_pres(id) ); +} + +/* + * Send instant messaging outside dialog. + */ +void Buddy::sendInstantMessage(const SendInstantMessageParam &prm) throw(Error) +{ + BuddyInfo bi = getInfo(); + + pj_str_t to = str2Pj(bi.contact.empty()? bi.uri : bi.contact); + pj_str_t mime_type = str2Pj(prm.contentType); + pj_str_t content = str2Pj(prm.content); + void *user_data = (void*)prm.userData; + pjsua_msg_data msg_data; + prm.txOption.toPj(msg_data); + + PJSUA2_CHECK_EXPR( pjsua_im_send(acc->getId(), &to, &mime_type, &content, + &msg_data, user_data) ); +} + +/* + * Send typing indication outside dialog. + */ +void Buddy::sendTypingIndication(const SendTypingIndicationParam &prm) + throw(Error) +{ + BuddyInfo bi = getInfo(); + + pj_str_t to = str2Pj(bi.contact.empty()? bi.uri : bi.contact); + pjsua_msg_data msg_data; + prm.txOption.toPj(msg_data); + + PJSUA2_CHECK_EXPR( pjsua_im_typing(acc->getId(), &to, prm.isTyping, + &msg_data) ); +} + diff --git a/pjsip/src/pjsua2/siptypes.cpp b/pjsip/src/pjsua2/siptypes.cpp new file mode 100644 index 00000000..f1d8bf2b --- /dev/null +++ b/pjsip/src/pjsua2/siptypes.cpp @@ -0,0 +1,590 @@ +/* $Id$ */ +/* + * 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/types.hpp> +#include <pjsua2/siptypes.hpp> +#include "util.hpp" + +using namespace pj; +using namespace std; + +#define THIS_FILE "siptypes.cpp" + +/////////////////////////////////////////////////////////////////////////////// +namespace pj +{ +void readIntVector( ContainerNode &node, + const string &array_name, + IntVector &v) throw(Error) +{ + ContainerNode array_node = node.readArray(array_name); + v.resize(0); + while (array_node.hasUnread()) { + v.push_back((int)array_node.readNumber()); + } +} + +void writeIntVector(ContainerNode &node, + const string &array_name, + const IntVector &v) throw(Error) +{ + ContainerNode array_node = node.writeNewArray(array_name); + for (unsigned i=0; i<v.size(); ++i) { + array_node.writeNumber("", (float)v[i]); + } +} + +void readQosParams( ContainerNode &node, + pj_qos_params &qos) throw(Error) +{ + ContainerNode this_node = node.readContainer("qosParams"); + + NODE_READ_NUM_T( this_node, pj_uint8_t, qos.flags); + NODE_READ_NUM_T( this_node, pj_uint8_t, qos.dscp_val); + NODE_READ_NUM_T( this_node, pj_uint8_t, qos.so_prio); + NODE_READ_NUM_T( this_node, pj_qos_wmm_prio, qos.wmm_prio); +} + +void writeQosParams( ContainerNode &node, + const pj_qos_params &qos) throw(Error) +{ + ContainerNode this_node = node.writeNewContainer("qosParams"); + + NODE_WRITE_NUM_T( this_node, pj_uint8_t, qos.flags); + NODE_WRITE_NUM_T( this_node, pj_uint8_t, qos.dscp_val); + NODE_WRITE_NUM_T( this_node, pj_uint8_t, qos.so_prio); + NODE_WRITE_NUM_T( this_node, pj_qos_wmm_prio, qos.wmm_prio); +} + +void readSipHeaders( const ContainerNode &node, + const string &array_name, + SipHeaderVector &headers) throw(Error) +{ + ContainerNode headers_node = node.readArray(array_name); + headers.resize(0); + while (headers_node.hasUnread()) { + SipHeader hdr; + + ContainerNode header_node = headers_node.readContainer("header"); + hdr.hName = header_node.readString("hname"); + hdr.hValue = header_node.readString("hvalue"); + headers.push_back(hdr); + } +} + +void writeSipHeaders(ContainerNode &node, + const string &array_name, + const SipHeaderVector &headers) throw(Error) +{ + ContainerNode headers_node = node.writeNewArray(array_name); + for (unsigned i=0; i<headers.size(); ++i) { + ContainerNode header_node = headers_node.writeNewContainer("header"); + header_node.writeString("hname", headers[i].hName); + header_node.writeString("hvalue", headers[i].hValue); + } +} + +} // namespace +/////////////////////////////////////////////////////////////////////////////// + +AuthCredInfo::AuthCredInfo() +: scheme("digest"), realm("*"), dataType(0) +{ +} + +AuthCredInfo::AuthCredInfo(const string ¶m_scheme, + const string ¶m_realm, + const string ¶m_user_name, + const int param_data_type, + const string param_data) +: scheme(param_scheme), realm(param_realm), username(param_user_name), + dataType(param_data_type), data(param_data) +{ +} + +void AuthCredInfo::readObject(const ContainerNode &node) throw(Error) +{ + ContainerNode this_node = node.readContainer("AuthCredInfo"); + + NODE_READ_STRING( this_node, scheme); + NODE_READ_STRING( this_node, realm); + NODE_READ_STRING( this_node, username); + NODE_READ_INT ( this_node, dataType); + NODE_READ_STRING( this_node, data); + NODE_READ_STRING( this_node, akaK); + NODE_READ_STRING( this_node, akaOp); + NODE_READ_STRING( this_node, akaAmf); +} + +void AuthCredInfo::writeObject(ContainerNode &node) const throw(Error) +{ + ContainerNode this_node = node.writeNewContainer("AuthCredInfo"); + + NODE_WRITE_STRING( this_node, scheme); + NODE_WRITE_STRING( this_node, realm); + NODE_WRITE_STRING( this_node, username); + NODE_WRITE_INT ( this_node, dataType); + NODE_WRITE_STRING( this_node, data); + NODE_WRITE_STRING( this_node, akaK); + NODE_WRITE_STRING( this_node, akaOp); + NODE_WRITE_STRING( this_node, akaAmf); +} + +/////////////////////////////////////////////////////////////////////////////// + +TlsConfig::TlsConfig() +{ + pjsip_tls_setting ts; + pjsip_tls_setting_default(&ts); + this->fromPj(ts); +} + +pjsip_tls_setting TlsConfig::toPj() const +{ + pjsip_tls_setting ts; + + ts.ca_list_file = str2Pj(this->CaListFile); + ts.cert_file = str2Pj(this->certFile); + ts.privkey_file = str2Pj(this->privKeyFile); + ts.password = str2Pj(this->password); + ts.method = this->method; + ts.ciphers_num = this->ciphers.size(); + // The following will only work if sizeof(enum)==sizeof(int) + pj_assert(sizeof(ts.ciphers[0]) == sizeof(int)); + ts.ciphers = ts.ciphers_num? + (pj_ssl_cipher*)&this->ciphers[0] : NULL; + ts.verify_server = this->verifyServer; + ts.verify_client = this->verifyClient; + ts.require_client_cert = this->requireClientCert; + ts.timeout.sec = this->msecTimeout / 1000; + ts.timeout.msec = this->msecTimeout % 1000; + ts.qos_type = this->qosType; + ts.qos_params = this->qosParams; + ts.qos_ignore_error = this->qosIgnoreError; + + return ts; +} + +void TlsConfig::fromPj(const pjsip_tls_setting &prm) +{ + this->CaListFile = pj2Str(prm.ca_list_file); + this->certFile = pj2Str(prm.cert_file); + this->privKeyFile = pj2Str(prm.privkey_file); + this->password = pj2Str(prm.password); + this->method = (pjsip_ssl_method)prm.method; + // The following will only work if sizeof(enum)==sizeof(int) + pj_assert(sizeof(prm.ciphers[0]) == sizeof(int)); + this->ciphers = IntVector(prm.ciphers, prm.ciphers+prm.ciphers_num); + this->verifyServer = PJ2BOOL(prm.verify_server); + this->verifyClient = PJ2BOOL(prm.verify_client); + this->requireClientCert = PJ2BOOL(prm.require_client_cert); + this->msecTimeout = PJ_TIME_VAL_MSEC(prm.timeout); + this->qosType = prm.qos_type; + this->qosParams = prm.qos_params; + this->qosIgnoreError = PJ2BOOL(prm.qos_ignore_error); +} + +void TlsConfig::readObject(const ContainerNode &node) throw(Error) +{ + ContainerNode this_node = node.readContainer("TlsConfig"); + + NODE_READ_STRING ( this_node, CaListFile); + NODE_READ_STRING ( this_node, certFile); + NODE_READ_STRING ( this_node, privKeyFile); + NODE_READ_STRING ( this_node, password); + NODE_READ_NUM_T ( this_node, pjsip_ssl_method, method); + readIntVector ( this_node, "ciphers", ciphers); + NODE_READ_BOOL ( this_node, verifyServer); + NODE_READ_BOOL ( this_node, verifyClient); + NODE_READ_BOOL ( this_node, requireClientCert); + NODE_READ_UNSIGNED( this_node, msecTimeout); + NODE_READ_NUM_T ( this_node, pj_qos_type, qosType); + readQosParams ( this_node, qosParams); + NODE_READ_BOOL ( this_node, qosIgnoreError); +} + +void TlsConfig::writeObject(ContainerNode &node) const throw(Error) +{ + ContainerNode this_node = node.writeNewContainer("TlsConfig"); + + NODE_WRITE_STRING ( this_node, CaListFile); + NODE_WRITE_STRING ( this_node, certFile); + NODE_WRITE_STRING ( this_node, privKeyFile); + NODE_WRITE_STRING ( this_node, password); + NODE_WRITE_NUM_T ( this_node, pjsip_ssl_method, method); + writeIntVector ( this_node, "ciphers", ciphers); + NODE_WRITE_BOOL ( this_node, verifyServer); + NODE_WRITE_BOOL ( this_node, verifyClient); + NODE_WRITE_BOOL ( this_node, requireClientCert); + NODE_WRITE_UNSIGNED( this_node, msecTimeout); + NODE_WRITE_NUM_T ( this_node, pj_qos_type, qosType); + writeQosParams ( this_node, qosParams); + NODE_WRITE_BOOL ( this_node, qosIgnoreError); +} + +/////////////////////////////////////////////////////////////////////////////// + +TransportConfig::TransportConfig() +{ + pjsua_transport_config tc; + pjsua_transport_config_default(&tc); + this->fromPj(tc); +} + +void TransportConfig::fromPj(const pjsua_transport_config &prm) +{ + this->port = prm.port; + this->portRange = prm.port_range; + this->publicAddress = pj2Str(prm.public_addr); + this->boundAddress = pj2Str(prm.bound_addr); + this->tlsConfig.fromPj(prm.tls_setting); + this->qosType = prm.qos_type; + this->qosParams = prm.qos_params; +} + +pjsua_transport_config TransportConfig::toPj() const +{ + pjsua_transport_config tc; + + tc.port = this->port; + tc.port_range = this->portRange; + tc.public_addr = str2Pj(this->publicAddress); + tc.bound_addr = str2Pj(this->boundAddress); + tc.tls_setting = this->tlsConfig.toPj(); + tc.qos_type = this->qosType; + tc.qos_params = this->qosParams; + + return tc; +} + +void TransportConfig::readObject(const ContainerNode &node) throw(Error) +{ + ContainerNode this_node = node.readContainer("TransportConfig"); + + NODE_READ_UNSIGNED ( this_node, port); + NODE_READ_UNSIGNED ( this_node, portRange); + NODE_READ_STRING ( this_node, publicAddress); + NODE_READ_STRING ( this_node, boundAddress); + NODE_READ_NUM_T ( this_node, pj_qos_type, qosType); + readQosParams ( this_node, qosParams); + NODE_READ_OBJ ( this_node, tlsConfig); +} + +void TransportConfig::writeObject(ContainerNode &node) const throw(Error) +{ + ContainerNode this_node = node.writeNewContainer("TransportConfig"); + + NODE_WRITE_UNSIGNED ( this_node, port); + NODE_WRITE_UNSIGNED ( this_node, portRange); + NODE_WRITE_STRING ( this_node, publicAddress); + NODE_WRITE_STRING ( this_node, boundAddress); + NODE_WRITE_NUM_T ( this_node, pj_qos_type, qosType); + writeQosParams ( this_node, qosParams); + NODE_WRITE_OBJ ( this_node, tlsConfig); +} + +/////////////////////////////////////////////////////////////////////////////// + +void TransportInfo::fromPj(const pjsua_transport_info &info) +{ + this->id = info.id; + this->type = info.type; + this->typeName = pj2Str(info.type_name); + this->info = pj2Str(info.info); + this->flags = info.flag; + + char straddr[PJ_INET6_ADDRSTRLEN+10]; + pj_sockaddr_print(&info.local_addr, straddr, sizeof(straddr), 3); + this->localAddress = straddr; + + pj_ansi_snprintf(straddr, sizeof(straddr), "%.*s:%d", + (int)info.local_name.host.slen, + info.local_name.host.ptr, + info.local_name.port); + this->localName = straddr; + this->usageCount = info.usage_count; +} + +/////////////////////////////////////////////////////////////////////////////// + +SipRxData::SipRxData() +: pjRxData(NULL) +{ +} + +void SipRxData::fromPj(pjsip_rx_data &rdata) +{ + char straddr[PJ_INET6_ADDRSTRLEN+10]; + + info = pjsip_rx_data_get_info(&rdata); + wholeMsg = string(rdata.msg_info.msg_buf, rdata.msg_info.len); + pj_sockaddr_print(&rdata.pkt_info.src_addr, straddr, sizeof(straddr), 3); + srcAddress = straddr; + pjRxData = (void *)&rdata; +} + +/////////////////////////////////////////////////////////////////////////////// + +void SipMediaType::fromPj(const pjsip_media_type &prm) +{ + type = pj2Str(prm.type); + subType = pj2Str(prm.subtype); +} + +pjsip_media_type SipMediaType::toPj() const +{ + pjsip_media_type pj_mt; + pj_bzero(&pj_mt, sizeof(pj_mt)); + pj_mt.type = str2Pj(type); + pj_mt.subtype = str2Pj(subType); + return pj_mt; +} + +/////////////////////////////////////////////////////////////////////////////// + +void SipHeader::fromPj(const pjsip_hdr *hdr) throw(Error) +{ + char buf[256]; + + int len = pjsip_hdr_print_on((void*)hdr, buf, sizeof(buf)-1); + if (len <= 0) + PJSUA2_RAISE_ERROR(PJ_ETOOSMALL); + buf[len] = '\0'; + + char *pos = strchr(buf, ':'); + if (!pos) + PJSUA2_RAISE_ERROR(PJSIP_EINVALIDHDR); + + // Trim white space after header name + char *end_name = pos; + while (end_name>buf && pj_isspace(*(end_name-1))) --end_name; + + // Trim whitespaces after colon + char *start_val = pos+1; + while (*start_val && pj_isspace(*start_val)) ++start_val; + + hName = string(buf, end_name); + hValue = string(start_val); +} + +pjsip_generic_string_hdr &SipHeader::toPj() const +{ + pj_str_t hname = str2Pj(hName); + pj_str_t hvalue = str2Pj(hValue); + + pjsip_generic_string_hdr_init2(&pjHdr, &hname, &hvalue); + return pjHdr; +} + +/////////////////////////////////////////////////////////////////////////////// + +void SipMultipartPart::fromPj(const pjsip_multipart_part &prm) throw(Error) +{ + headers.clear(); + pjsip_hdr* pj_hdr = prm.hdr.next; + while (pj_hdr != &prm.hdr) { + SipHeader sh; + sh.fromPj(pj_hdr); + headers.push_back(sh); + pj_hdr = pj_hdr->next; + } + + if (!prm.body) + PJSUA2_RAISE_ERROR(PJ_EINVAL); + + contentType.fromPj(prm.body->content_type); + body = string((char*)prm.body->data, prm.body->len); +} + +pjsip_multipart_part& SipMultipartPart::toPj() const +{ + pj_list_init(&pjMpp.hdr); + for (unsigned i = 0; i < headers.size(); i++) { + pjsip_generic_string_hdr& pj_hdr = headers[i].toPj(); + pj_list_push_back(&pjMpp.hdr, &pj_hdr); + } + + pj_bzero(&pjMsgBody, sizeof(pjMsgBody)); + pjMsgBody.content_type = contentType.toPj(); + pjMsgBody.print_body = &pjsip_print_text_body; + pjMsgBody.clone_data = &pjsip_clone_text_data; + pjMsgBody.data = (void*)body.c_str(); + pjMsgBody.len = body.size(); + pjMpp.body = &pjMsgBody; + + return pjMpp; +} + +/////////////////////////////////////////////////////////////////////////////// + +SipEvent::SipEvent() +: type(PJSIP_EVENT_UNKNOWN), pjEvent(NULL) +{ +} + +void SipEvent::fromPj(const pjsip_event &ev) +{ + type = ev.type; + if (type == PJSIP_EVENT_TIMER) { + body.timer.entry = ev.body.timer.entry; + } else if (type == PJSIP_EVENT_TSX_STATE) { + body.tsxState.prevState = (pjsip_tsx_state_e) + ev.body.tsx_state.prev_state; + body.tsxState.tsx.fromPj(*ev.body.tsx_state.tsx); + if (body.tsxState.type == PJSIP_EVENT_TX_MSG) { + if (ev.body.tsx_state.src.tdata) + body.tsxState.src.tdata.fromPj(*ev.body.tsx_state.src.tdata); + } else if (body.tsxState.type == PJSIP_EVENT_RX_MSG) { + if (ev.body.tsx_state.src.rdata) + body.tsxState.src.rdata.fromPj(*ev.body.tsx_state.src.rdata); + } else if (body.tsxState.type == PJSIP_EVENT_TRANSPORT_ERROR) { + body.tsxState.src.status = ev.body.tsx_state.src.status; + } else if (body.tsxState.type == PJSIP_EVENT_TIMER) { + body.tsxState.src.timer = ev.body.tsx_state.src.timer; + } else if (body.tsxState.type == PJSIP_EVENT_USER) { + body.tsxState.src.data = ev.body.tsx_state.src.data; + } + } else if (type == PJSIP_EVENT_TX_MSG) { + if (ev.body.tx_msg.tdata) + body.txMsg.tdata.fromPj(*ev.body.tx_msg.tdata); + } else if (type == PJSIP_EVENT_RX_MSG) { + if (ev.body.rx_msg.rdata) + body.rxMsg.rdata.fromPj(*ev.body.rx_msg.rdata); + } else if (type == PJSIP_EVENT_TRANSPORT_ERROR) { + if (ev.body.tx_error.tdata) + body.txError.tdata.fromPj(*ev.body.tx_error.tdata); + if (ev.body.tx_error.tsx) + body.txError.tsx.fromPj(*ev.body.tx_error.tsx); + } else if (type == PJSIP_EVENT_USER) { + body.user.user1 = ev.body.user.user1; + body.user.user2 = ev.body.user.user2; + body.user.user3 = ev.body.user.user3; + body.user.user4 = ev.body.user.user4; + } + pjEvent = (void *)&ev; +} + +SipTxData::SipTxData() +: pjTxData(NULL) +{ +} + +void SipTxData::fromPj(pjsip_tx_data &tdata) +{ + char straddr[PJ_INET6_ADDRSTRLEN+10]; + + info = pjsip_tx_data_get_info(&tdata); + pjsip_tx_data_encode(&tdata); + wholeMsg = string(tdata.buf.start, tdata.buf.end - tdata.buf.start); + if (pj_sockaddr_has_addr(&tdata.tp_info.dst_addr)) { + pj_sockaddr_print(&tdata.tp_info.dst_addr, straddr, sizeof(straddr), 3); + dstAddress = straddr; + } else { + dstAddress = ""; + } + pjTxData = (void *)&tdata; +} + +SipTransaction::SipTransaction() +: role(PJSIP_ROLE_UAC), statusCode(0), pjTransaction(NULL) +{ +} + +void SipTransaction::fromPj(pjsip_transaction &tsx) +{ + this->role = tsx.role; + this->method = pj2Str(tsx.method.name); + this->statusCode = tsx.status_code; + this->statusText = pj2Str(tsx.status_text); + if (tsx.last_tx) + this->lastTx.fromPj(*tsx.last_tx); + else + this->lastTx.pjTxData = NULL; + this->pjTransaction = (void *)&tsx; +} + +bool SipTxOption::isEmpty() const +{ + return (targetUri == "" && headers.size() == 0 && contentType == "" && + msgBody == "" && multipartContentType.type == "" && + multipartContentType.subType == "" && multipartParts.size() == 0); +} + +void SipTxOption::fromPj(const pjsua_msg_data &prm) throw(Error) +{ + targetUri = pj2Str(prm.target_uri); + + headers.clear(); + pjsip_hdr* pj_hdr = prm.hdr_list.next; + while (pj_hdr != &prm.hdr_list) { + SipHeader sh; + sh.fromPj(pj_hdr); + headers.push_back(sh); + pj_hdr = pj_hdr->next; + } + + contentType = pj2Str(prm.content_type); + msgBody = pj2Str(prm.msg_body); + multipartContentType.fromPj(prm.multipart_ctype); + + multipartParts.clear(); + pjsip_multipart_part* pj_mp = prm.multipart_parts.next; + while (pj_mp != &prm.multipart_parts) { + SipMultipartPart smp; + smp.fromPj(*pj_mp); + multipartParts.push_back(smp); + pj_mp = pj_mp->next; + } +} + +void SipTxOption::toPj(pjsua_msg_data &msg_data) const +{ + unsigned i; + + pjsua_msg_data_init(&msg_data); + + msg_data.target_uri = str2Pj(targetUri); + + pj_list_init(&msg_data.hdr_list); + for (i = 0; i < headers.size(); i++) { + pjsip_generic_string_hdr& pj_hdr = headers[i].toPj(); + pj_list_push_back(&msg_data.hdr_list, &pj_hdr); + } + + msg_data.content_type = str2Pj(contentType); + msg_data.msg_body = str2Pj(msgBody); + msg_data.multipart_ctype = multipartContentType.toPj(); + + pj_list_init(&msg_data.multipart_parts); + for (i = 0; i < multipartParts.size(); i++) { + pjsip_multipart_part& pj_part = multipartParts[i].toPj(); + pj_list_push_back(&msg_data.multipart_parts, &pj_part); + } +} + +////////////////////////////////////////////////////////////////////////////// + +SendInstantMessageParam::SendInstantMessageParam() +: contentType("text/plain"), content(""), userData(NULL) +{ +} + +SendTypingIndicationParam::SendTypingIndicationParam() +: isTyping(false) +{ +} diff --git a/pjsip/src/pjsua2/types.cpp b/pjsip/src/pjsua2/types.cpp new file mode 100644 index 00000000..26740cc7 --- /dev/null +++ b/pjsip/src/pjsua2/types.cpp @@ -0,0 +1,95 @@ +/* $Id$ */ +/* + * 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/types.hpp> +#include "util.hpp" + +using namespace pj; +using namespace std; + +#define THIS_FILE "types.cpp" + +/////////////////////////////////////////////////////////////////////////////// + +Error::Error() +: status(PJ_SUCCESS), srcLine(0) +{ +} + +Error::Error( pj_status_t prm_status, + const string &prm_title, + const string &prm_reason, + const string &prm_src_file, + int prm_src_line) +: status(prm_status), title(prm_title), reason(prm_reason), + srcFile(prm_src_file), srcLine(prm_src_line) +{ + if (this->status != PJ_SUCCESS && prm_reason.empty()) { + char errmsg[PJ_ERR_MSG_SIZE]; + pj_strerror(this->status, errmsg, sizeof(errmsg)); + this->reason = errmsg; + } +} + +string Error::info(bool multi_line) const +{ + string output; + + if (status==PJ_SUCCESS) { + output = "No error"; + } else if (!multi_line) { + char temp[80]; + + if (!title.empty()) { + output += title + " error: "; + } + snprintf(temp, sizeof(temp), " (status=%d)", status); + output += reason + temp; + if (!srcFile.empty()) { + output += " ["; + output += srcFile; + snprintf(temp, sizeof(temp), ":%d]", srcLine); + output += temp; + } + } else { + char temp[80]; + + if (!title.empty()) { + output += string("Title: ") + title + "\n"; + } + + snprintf(temp, sizeof(temp), "%d\n", status); + output += string("Code: ") + temp; + output += string("Description: ") + reason + "\n"; + if (!srcFile.empty()) { + snprintf(temp, sizeof(temp), ":%d\n", srcLine); + output += string("Location: ") + srcFile + temp; + } + } + + return output; +} + +/////////////////////////////////////////////////////////////////////////////// + +void TimeValue::fromPj(const pj_time_val &prm) +{ + this->sec = prm.sec; + this->msec = prm.msec; +} + diff --git a/pjsip/src/pjsua2/util.hpp b/pjsip/src/pjsua2/util.hpp new file mode 100644 index 00000000..ae72af63 --- /dev/null +++ b/pjsip/src/pjsua2/util.hpp @@ -0,0 +1,45 @@ +/* $Id$ */ +/* + * 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/types.hpp> +#include <string> + +#define PJ2BOOL(var) ((var) != PJ_FALSE) + +namespace pj +{ +using std::string; + +inline pj_str_t str2Pj(const string &input_str) +{ + pj_str_t output_str; + output_str.ptr = (char*)input_str.c_str(); + output_str.slen = input_str.size(); + return output_str; +} + +inline string pj2Str(const pj_str_t &input_str) +{ + if (input_str.ptr) + return string(input_str.ptr, input_str.slen); + return string(); +} + + +} // namespace |