blob: 254d10cc7bb3c4ee204e7ce4bfe03aba44ce09fe [file] [log] [blame]
Alexandre Lision0e143012014-01-22 11:02:46 -05001/* $Id: call.cpp 4704 2014-01-16 05:30:46Z ming $ */
2/*
3 * Copyright (C) 2012-2013 Teluu Inc. (http://www.teluu.com)
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 */
19#include <pjsua2/account.hpp>
20#include <pjsua2/call.hpp>
21#include <pjsua2/endpoint.hpp>
22#include <pj/ctype.h>
23#include "util.hpp"
24
25using namespace pj;
26using namespace std;
27
28#define THIS_FILE "call.cpp"
29
30///////////////////////////////////////////////////////////////////////////////
31
32#define SDP_BUFFER_SIZE 1024
33
34MathStat::MathStat()
35: n(0), max(0), min(0), last(0), mean(0)
36{
37}
38
39void MathStat::fromPj(const pj_math_stat &prm)
40{
41 this->n = prm.n;
42 this->max = prm.max;
43 this->min = prm.min;
44 this->last = prm.last;
45 this->mean = prm.mean;
46}
47
48void RtcpStreamStat::fromPj(const pjmedia_rtcp_stream_stat &prm)
49{
50 this->update.fromPj(prm.update);
51 this->updateCount = prm.update_cnt;
52 this->pkt = (unsigned)prm.pkt;
53 this->bytes = (unsigned)prm.bytes;
54 this->discard = prm.discard;
55 this->loss = prm.loss;
56 this->reorder = prm.loss;
57 this->dup = prm.dup;
58 this->lossPeriodUsec.fromPj(prm.loss_period);
59 this->lossType.burst = prm.loss_type.burst;
60 this->lossType.random = prm.loss_type.random;
61 this->jitterUsec.fromPj(prm.jitter);
62}
63
64void RtcpSdes::fromPj(const pjmedia_rtcp_sdes &prm)
65{
66 this->cname = pj2Str(prm.cname);
67 this->name = pj2Str(prm.name);
68 this->email = pj2Str(prm.email);
69 this->phone = pj2Str(prm.phone);
70 this->loc = pj2Str(prm.loc);
71 this->tool = pj2Str(prm.tool);
72 this->note = pj2Str(prm.note);
73}
74
75void RtcpStat::fromPj(const pjmedia_rtcp_stat &prm)
76{
77 this->start.fromPj(prm.start);
78 this->txStat.fromPj(prm.tx);
79 this->rxStat.fromPj(prm.rx);
80 this->rttUsec.fromPj(prm.rtt);
81 this->rtpTxLastTs = prm.rtp_tx_last_ts;
82 this->rtpTxLastSeq = prm.rtp_tx_last_seq;
83#if defined(PJMEDIA_RTCP_STAT_HAS_IPDV) && PJMEDIA_RTCP_STAT_HAS_IPDV!=0
84 this->rxIpdvUsec.fromPj(prm.rx_ipdv);
85#endif
86#if defined(PJMEDIA_RTCP_STAT_HAS_RAW_JITTER) && \
87 PJMEDIA_RTCP_STAT_HAS_RAW_JITTER!=0
88 this->rxRawJitterUsec.fromPj(prm.rx_raw_jitter);
89#endif
90 this->peerSdes.fromPj(prm.peer_sdes);
91}
92
93void JbufState::fromPj(const pjmedia_jb_state &prm)
94{
95 this->frameSize = prm.frame_size;
96 this->minPrefetch = prm.min_prefetch;
97 this->maxPrefetch = prm.max_prefetch;
98 this->burst = prm.burst;
99 this->prefetch = prm.prefetch;
100 this->size = prm.size;
101 this->avgDelayMsec = prm.avg_delay;
102 this->minDelayMsec = prm.min_delay;
103 this->maxDelayMsec = prm.max_delay;
104 this->devDelayMsec = prm.dev_delay;
105 this->avgBurst = prm.avg_burst;
106 this->lost = prm.lost;
107 this->discard = prm.discard;
108 this->empty = prm.empty;
109}
110
111void SdpSession::fromPj(const pjmedia_sdp_session &sdp)
112{
113 char buf[SDP_BUFFER_SIZE];
114 int len;
115
116 len = pjmedia_sdp_print(&sdp, buf, sizeof(buf));
117 wholeSdp = (len > -1? string(buf, len): "");
118 pjSdpSession = (void *)&sdp;
119}
120
121void MediaEvent::fromPj(const pjmedia_event &ev)
122{
123 type = ev.type;
124 if (type == PJMEDIA_EVENT_FMT_CHANGED) {
125 data.fmtChanged.newWidth = ev.data.fmt_changed.new_fmt.det.vid.size.w;
126 data.fmtChanged.newHeight = ev.data.fmt_changed.new_fmt.det.vid.size.h;
127 }
128 pjMediaEvent = (void *)&ev;
129}
130
131void MediaTransportInfo::fromPj(const pjmedia_transport_info &info)
132{
133 char straddr[PJ_INET6_ADDRSTRLEN+10];
134
135 pj_sockaddr_print(&info.src_rtp_name, straddr, sizeof(straddr), 3);
136 srcRtpName = straddr;
137 pj_sockaddr_print(&info.src_rtcp_name, straddr, sizeof(straddr), 3);
138 srcRtcpName = straddr;
139}
140
141//////////////////////////////////////////////////////////////////////////////
142
143/* Call Audio Media. */
144class CallAudioMedia : public AudioMedia
145{
146public:
147 /*
148 * Set the conference port identification associated with the
149 * call audio media.
150 */
151 void setPortId(int id);
152};
153
154
155void CallAudioMedia::setPortId(int id)
156{
157 this->id = id;
158}
159
160CallOpParam::CallOpParam(bool useDefaultCallSetting)
161: statusCode(pjsip_status_code(0)), reason(""), options(0)
162{
163 if (useDefaultCallSetting)
164 opt = CallSetting(true);
165}
166
167CallSendRequestParam::CallSendRequestParam()
168: method("")
169{
170}
171
172CallVidSetStreamParam::CallVidSetStreamParam()
173{
174#if PJSUA_HAS_VIDEO
175 pjsua_call_vid_strm_op_param prm;
176
177 pjsua_call_vid_strm_op_param_default(&prm);
178 this->medIdx = prm.med_idx;
179 this->dir = prm.dir;
180 this->capDev = prm.cap_dev;
181#endif
182}
183
184CallSetting::CallSetting(pj_bool_t useDefaultValues)
185{
186 if (useDefaultValues) {
187 pjsua_call_setting setting;
188
189 pjsua_call_setting_default(&setting);
190 fromPj(setting);
191 } else {
192 flag = 0;
193 reqKeyframeMethod = 0;
194 audioCount = 0;
195 videoCount = 0;
196 }
197}
198
199bool CallSetting::isEmpty() const
200{
201 return (flag == 0 && reqKeyframeMethod == 0 && audioCount == 0 &&
202 videoCount == 0);
203}
204
205void CallSetting::fromPj(const pjsua_call_setting &prm)
206{
207 this->flag = prm.flag;
208 this->reqKeyframeMethod = prm.req_keyframe_method;
209 this->audioCount = prm.aud_cnt;
210 this->videoCount = prm.vid_cnt;
211}
212
213pjsua_call_setting CallSetting::toPj() const
214{
215 pjsua_call_setting setting;
216
217 setting.flag = this->flag;
218 setting.req_keyframe_method = this->reqKeyframeMethod;
219 setting.aud_cnt = this->audioCount;
220 setting.vid_cnt = this->videoCount;
221
222 return setting;
223}
224
225
226CallMediaInfo::CallMediaInfo()
227{
228}
229
230void CallMediaInfo::fromPj(const pjsua_call_media_info &prm)
231{
232 this->index = prm.index;
233 this->type = prm.type;
234 this->dir = prm.dir;
235 this->status = prm.status;
236 if (this->type == PJMEDIA_TYPE_AUDIO) {
237 this->audioConfSlot = (int)prm.stream.aud.conf_slot;
238 } else if (this->type == PJMEDIA_TYPE_VIDEO) {
239 this->videoIncomingWindowId = prm.stream.vid.win_in;
240 this->videoCapDev = prm.stream.vid.cap_dev;
241 }
242}
243
244void CallInfo::fromPj(const pjsua_call_info &pci)
245{
246 unsigned mi;
247
248 id = pci.id;
249 role = pci.role;
250 accId = pci.acc_id;
251 localUri = pj2Str(pci.local_info);
252 localContact = pj2Str(pci.local_contact);
253 remoteUri = pj2Str(pci.remote_info);
254 remoteContact = pj2Str(pci.remote_contact);
255 callIdString = pj2Str(pci.call_id);
256 setting.fromPj(pci.setting);
257 state = pci.state;
258 stateText = pj2Str(pci.state_text);
259 lastStatusCode = pci.last_status;
260 lastReason = pj2Str(pci.last_status_text);
261 connectDuration.fromPj(pci.connect_duration);
262 totalDuration.fromPj(pci.total_duration);
263 remOfferer = PJ2BOOL(pci.rem_offerer);
264 remAudioCount = pci.rem_aud_cnt;
265 remVideoCount = pci.rem_vid_cnt;
266
267 for (mi = 0; mi < pci.media_cnt; mi++) {
268 CallMediaInfo med;
269
270 med.fromPj(pci.media[mi]);
271 media.push_back(med);
272 }
273 for (mi = 0; mi < pci.prov_media_cnt; mi++) {
274 CallMediaInfo med;
275
276 med.fromPj(pci.prov_media[mi]);
277 provMedia.push_back(med);
278 }
279}
280
281void StreamInfo::fromPj(const pjsua_stream_info &info)
282{
283 char straddr[PJ_INET6_ADDRSTRLEN+10];
284
285 type = info.type;
286 if (type == PJMEDIA_TYPE_AUDIO) {
287 proto = info.info.aud.proto;
288 dir = info.info.aud.dir;
289 pj_sockaddr_print(&info.info.aud.rem_addr, straddr, sizeof(straddr), 3);
290 remoteRtpAddress = straddr;
291 pj_sockaddr_print(&info.info.aud.rem_rtcp, straddr, sizeof(straddr), 3);
292 remoteRtcpAddress = straddr;
293 txPt = info.info.aud.tx_pt;
294 rxPt = info.info.aud.rx_pt;
295 codecName = pj2Str(info.info.aud.fmt.encoding_name);
296 codecClockRate = info.info.aud.fmt.clock_rate;
297 codecParam = info.info.aud.param;
298 } else if (type == PJMEDIA_TYPE_VIDEO) {
299 proto = info.info.vid.proto;
300 dir = info.info.vid.dir;
301 pj_sockaddr_print(&info.info.vid.rem_addr, straddr, sizeof(straddr), 3);
302 remoteRtpAddress = straddr;
303 pj_sockaddr_print(&info.info.vid.rem_rtcp, straddr, sizeof(straddr), 3);
304 remoteRtcpAddress = straddr;
305 txPt = info.info.vid.tx_pt;
306 rxPt = info.info.vid.rx_pt;
307 codecName = pj2Str(info.info.vid.codec_info.encoding_name);
308 codecClockRate = info.info.vid.codec_info.clock_rate;
309 codecParam = info.info.vid.codec_param;
310 }
311}
312
313void StreamStat::fromPj(const pjsua_stream_stat &prm)
314{
315 rtcp.fromPj(prm.rtcp);
316 jbuf.fromPj(prm.jbuf);
317}
318
319///////////////////////////////////////////////////////////////////////////////
320
321struct call_param
322{
323 pjsua_msg_data msg_data;
324 pjsua_msg_data *p_msg_data;
325 pjsua_call_setting opt;
326 pjsua_call_setting *p_opt;
327 pj_str_t reason;
328 pj_str_t *p_reason;
329
330public:
331 /**
332 * Default constructors with specified parameters.
333 */
334 call_param(const SipTxOption &tx_option);
335 call_param(const SipTxOption &tx_option, const CallSetting &setting,
336 const string &reason_str);
337};
338
339call_param::call_param(const SipTxOption &tx_option)
340{
341 if (tx_option.isEmpty()) {
342 p_msg_data = NULL;
343 } else {
344 tx_option.toPj(msg_data);
345 p_msg_data = &msg_data;
346 }
347
348 p_opt = NULL;
349 p_reason = NULL;
350}
351
352call_param::call_param(const SipTxOption &tx_option, const CallSetting &setting,
353 const string &reason_str)
354{
355 if (tx_option.isEmpty()) {
356 p_msg_data = NULL;
357 } else {
358 tx_option.toPj(msg_data);
359 p_msg_data = &msg_data;
360 }
361
362 if (setting.isEmpty()) {
363 p_opt = NULL;
364 } else {
365 opt = setting.toPj();
366 p_opt = &opt;
367 }
368
369 reason = str2Pj(reason_str);
370 p_reason = (reason.slen == 0? NULL: &reason);
371}
372
373Call::Call(Account& account, int call_id)
374: acc(account), id(call_id)
375{
376 if (call_id != PJSUA_INVALID_ID)
377 pjsua_call_set_user_data(call_id, this);
378}
379
380Call::~Call()
381{
382 /**
383 * If this instance is deleted, also hangup the corresponding call in
384 * PJSUA library.
385 */
386 if (id != PJSUA_INVALID_ID && pjsua_get_state() < PJSUA_STATE_CLOSING) {
387 pjsua_call_set_user_data(id, NULL);
388 if (isActive()) {
389 CallOpParam prm;
390 hangup(prm);
391 }
392 }
393}
394
395CallInfo Call::getInfo() const throw(Error)
396{
397 pjsua_call_info pj_ci;
398 CallInfo ci;
399
400 PJSUA2_CHECK_EXPR( pjsua_call_get_info(id, &pj_ci) );
401 ci.fromPj(pj_ci);
402 return ci;
403}
404
405bool Call::isActive() const
406{
407 if (id == PJSUA_INVALID_ID)
408 return false;
409
410 return (pjsua_call_is_active(id) != 0);
411}
412
413int Call::getId() const
414{
415 return id;
416}
417
418Call *Call::lookup(int call_id)
419{
420 Call *call = (Call*)pjsua_call_get_user_data(call_id);
421 if (call)
422 call->id = call_id;
423 return call;
424}
425
426bool Call::hasMedia() const
427{
428 return (pjsua_call_has_media(id) != 0);
429}
430
431Media *Call::getMedia(unsigned med_idx) const
432{
433 /* Check if the media index is valid and if the media has a valid port ID */
434 if (med_idx >= medias.size() ||
435 (medias[med_idx] && medias[med_idx]->getType() == PJMEDIA_TYPE_AUDIO &&
436 ((AudioMedia *)medias[med_idx])->getPortId() == PJSUA_INVALID_ID))
437 {
438 return NULL;
439 }
440
441 return medias[med_idx];
442}
443
444pjsip_dialog_cap_status Call::remoteHasCap(int htype,
445 const string &hname,
446 const string &token) const
447{
448 pj_str_t pj_hname = str2Pj(hname);
449 pj_str_t pj_token = str2Pj(token);
450
451 return pjsua_call_remote_has_cap(id, htype,
452 (htype == PJSIP_H_OTHER)? &pj_hname: NULL,
453 &pj_token);
454}
455
456void Call::setUserData(Token user_data)
457{
458 userData = user_data;
459}
460
461Token Call::getUserData() const
462{
463 return userData;
464}
465
466pj_stun_nat_type Call::getRemNatType() throw(Error)
467{
468 pj_stun_nat_type nat;
469
470 PJSUA2_CHECK_EXPR( pjsua_call_get_rem_nat_type(id, &nat) );
471
472 return nat;
473}
474
475void Call::makeCall(const string &dst_uri, const CallOpParam &prm) throw(Error)
476{
477 pj_str_t pj_dst_uri = str2Pj(dst_uri);
478 call_param param(prm.txOption, prm.opt, prm.reason);
479
480 PJSUA2_CHECK_EXPR( pjsua_call_make_call(acc.getId(), &pj_dst_uri,
481 param.p_opt, this,
482 param.p_msg_data, &id) );
483}
484
485void Call::answer(const CallOpParam &prm) throw(Error)
486{
487 call_param param(prm.txOption, prm.opt, prm.reason);
488
489 PJSUA2_CHECK_EXPR( pjsua_call_answer2(id, param.p_opt, prm.statusCode,
490 param.p_reason, param.p_msg_data) );
491}
492
493void Call::hangup(const CallOpParam &prm) throw(Error)
494{
495 call_param param(prm.txOption, prm.opt, prm.reason);
496
497 PJSUA2_CHECK_EXPR( pjsua_call_hangup(id, prm.statusCode, param.p_reason,
498 param.p_msg_data) );
499}
500
501void Call::setHold(const CallOpParam &prm) throw(Error)
502{
503 call_param param(prm.txOption, prm.opt, prm.reason);
504
505 PJSUA2_CHECK_EXPR( pjsua_call_set_hold2(id, prm.options, param.p_msg_data));
506}
507
508void Call::reinvite(const CallOpParam &prm) throw(Error)
509{
510 call_param param(prm.txOption, prm.opt, prm.reason);
511
512 PJSUA2_CHECK_EXPR( pjsua_call_reinvite2(id, param.p_opt, param.p_msg_data));
513}
514
515void Call::update(const CallOpParam &prm) throw(Error)
516{
517 call_param param(prm.txOption, prm.opt, prm.reason);
518
519 PJSUA2_CHECK_EXPR( pjsua_call_update2(id, param.p_opt, param.p_msg_data) );
520}
521
522void Call::xfer(const string &dest, const CallOpParam &prm) throw(Error)
523{
524 call_param param(prm.txOption);
525 pj_str_t pj_dest = str2Pj(dest);
526
527 PJSUA2_CHECK_EXPR( pjsua_call_xfer(id, &pj_dest, param.p_msg_data) );
528}
529
530void Call::xferReplaces(const Call& dest_call,
531 const CallOpParam &prm) throw(Error)
532{
533 call_param param(prm.txOption);
534
535 PJSUA2_CHECK_EXPR(pjsua_call_xfer_replaces(id, dest_call.getId(),
536 prm.options, param.p_msg_data) );
537}
538
539void Call::processRedirect(pjsip_redirect_op cmd) throw(Error)
540{
541 PJSUA2_CHECK_EXPR(pjsua_call_process_redirect(id, cmd));
542}
543
544void Call::dialDtmf(const string &digits) throw(Error)
545{
546 pj_str_t pj_digits = str2Pj(digits);
547
548 PJSUA2_CHECK_EXPR(pjsua_call_dial_dtmf(id, &pj_digits));
549}
550
551void Call::sendInstantMessage(const SendInstantMessageParam& prm)
552 throw(Error)
553{
554 pj_str_t mime_type = str2Pj(prm.contentType);
555 pj_str_t content = str2Pj(prm.content);
556 call_param param(prm.txOption);
557
558 PJSUA2_CHECK_EXPR(pjsua_call_send_im(id, &mime_type, &content,
559 param.p_msg_data, prm.userData) );
560}
561
562void Call::sendTypingIndication(const SendTypingIndicationParam &prm)
563 throw(Error)
564{
565 call_param param(prm.txOption);
566
567 PJSUA2_CHECK_EXPR(pjsua_call_send_typing_ind(id,
568 (prm.isTyping?
569 PJ_TRUE: PJ_FALSE),
570 param.p_msg_data) );
571}
572
573void Call::sendRequest(const CallSendRequestParam &prm) throw(Error)
574{
575 pj_str_t method = str2Pj(prm.method);
576 call_param param(prm.txOption);
577
578 PJSUA2_CHECK_EXPR(pjsua_call_send_request(id, &method, param.p_msg_data) );
579}
580
581string Call::dump(bool with_media, const string indent) throw(Error)
582{
583#if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0)
584 char buffer[1024 * 10];
585#else
586 char buffer[1024 * 3];
587#endif
588
589 PJSUA2_CHECK_EXPR(pjsua_call_dump(id, (with_media? PJ_TRUE: PJ_FALSE),
590 buffer, sizeof(buffer),
591 indent.c_str()));
592
593 return buffer;
594}
595
596int Call::vidGetStreamIdx() const
597{
598#if PJSUA_HAS_VIDEO
599 return pjsua_call_get_vid_stream_idx(id);
600#else
601 return PJSUA_INVALID_ID;
602#endif
603}
604
605bool Call::vidStreamIsRunning(int med_idx, pjmedia_dir dir) const
606{
607#if PJSUA_HAS_VIDEO
608 return pjsua_call_vid_stream_is_running(id, med_idx, dir);
609#else
610 PJ_UNUSED_ARG(med_idx);
611 PJ_UNUSED_ARG(dir);
612 return false;
613#endif
614}
615
616void Call::vidSetStream(pjsua_call_vid_strm_op op,
617 const CallVidSetStreamParam &param) throw(Error)
618{
619#if PJSUA_HAS_VIDEO
620 pjsua_call_vid_strm_op_param prm;
621
622 prm.med_idx = param.medIdx;
623 prm.dir = param.dir;
624 prm.cap_dev = param.capDev;
625 PJSUA2_CHECK_EXPR( pjsua_call_set_vid_strm(id, op, &prm) );
626#else
627 PJ_UNUSED_ARG(op);
628 PJ_UNUSED_ARG(param);
629 PJSUA2_RAISE_ERROR(PJ_EINVALIDOP);
630#endif
631}
632
633StreamInfo Call::getStreamInfo(unsigned med_idx) const throw(Error)
634{
635 pjsua_stream_info pj_si;
636 StreamInfo si;
637
638 PJSUA2_CHECK_EXPR( pjsua_call_get_stream_info(id, med_idx, &pj_si) );
639 si.fromPj(pj_si);
640 return si;
641}
642
643StreamStat Call::getStreamStat(unsigned med_idx) const throw(Error)
644{
645 pjsua_stream_stat pj_ss;
646 StreamStat ss;
647
648 PJSUA2_CHECK_EXPR( pjsua_call_get_stream_stat(id, med_idx, &pj_ss) );
649 ss.fromPj(pj_ss);
650 return ss;
651}
652
653MediaTransportInfo Call::getMedTransportInfo(unsigned med_idx) const
654 throw(Error)
655{
656 pjmedia_transport_info pj_mti;
657 MediaTransportInfo mti;
658
659 PJSUA2_CHECK_EXPR( pjsua_call_get_med_transport_info(id, med_idx,
660 &pj_mti) );
661 mti.fromPj(pj_mti);
662 return mti;
663}
664
665void Call::processMediaUpdate(OnCallMediaStateParam &prm)
666{
667 pjsua_call_info pj_ci;
668 unsigned mi;
669
670 if (pjsua_call_get_info(id, &pj_ci) == PJ_SUCCESS) {
671 for (mi = 0; mi < pj_ci.media_cnt; mi++) {
672 if (mi >= medias.size()) {
673 if (pj_ci.media[mi].type == PJMEDIA_TYPE_AUDIO) {
674 medias.push_back(new CallAudioMedia);
675 } else {
676 medias.push_back(NULL);
677 }
678 }
679
680 if (pj_ci.media[mi].type == PJMEDIA_TYPE_AUDIO) {
681 CallAudioMedia *aud_med = (CallAudioMedia *)medias[mi];
682
683 aud_med->setPortId(pj_ci.media[mi].stream.aud.conf_slot);
684 /* Add media if the conference slot ID is valid. */
685 if (pj_ci.media[mi].stream.aud.conf_slot != PJSUA_INVALID_ID)
686 {
687 Endpoint::instance().mediaAdd((AudioMedia &)*aud_med);
688 } else {
689 Endpoint::instance().mediaRemove((AudioMedia &)*aud_med);
690 }
691 }
692 }
693 }
694
695 /* Call media state callback. */
696 onCallMediaState(prm);
697}
698
699void Call::processStateChange(OnCallStateParam &prm)
700{
701 pjsua_call_info pj_ci;
702 unsigned mi;
703
704 if (pjsua_call_get_info(id, &pj_ci) == PJ_SUCCESS &&
705 pj_ci.state == PJSIP_INV_STATE_DISCONNECTED)
706 {
707 /* Clear medias. */
708 for (mi = 0; mi < medias.size(); mi++) {
709 if (medias[mi])
710 delete medias[mi];
711 }
712 medias.clear();
713 }
714
715 onCallState(prm);
716 /* If the state is DISCONNECTED, this call may have already been deleted
717 * by the application in the callback, so do not access it anymore here.
718 */
719}