blob: 69a91829c3b9497e4713204db72d45501c03bbe4 [file] [log] [blame]
/* $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 &param) 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.
*/
}