diff options
Diffstat (limited to 'pjsip/src/pjsua2/call.cpp')
-rw-r--r-- | pjsip/src/pjsua2/call.cpp | 719 |
1 files changed, 719 insertions, 0 deletions
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. + */ +} |