text: /* $Id: call.cpp 4885 2014-08-12 12:12:49Z nanang $ */
  /*
   * 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()
  {
      /* Remove reference to this instance from PJSUA library */
      if (id != PJSUA_INVALID_ID)
  	pjsua_call_set_user_data(id, NULL);
  
      /*
       * If this instance is deleted, also hangup the corresponding call in
       * PJSUA library.
       */
      if (pjsua_get_state() < PJSUA_STATE_CLOSING && 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) == PJ_TRUE);
  #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.
       */
  }
  