| /* $Id: call.cpp 4704 2014-01-16 05:30:46Z ming $ */ |
| /* |
| * 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. |
| */ |
| } |