diff --git a/daemon/src/audio/audiortp/audio_rtp_record_handler.cpp b/daemon/src/audio/audiortp/audio_rtp_record_handler.cpp index 4ab82a27c286dbc087d1edd85152ade738a2fc66..2735ccfb32083bc57a88a55665060fbc52cfd583 100644 --- a/daemon/src/audio/audiortp/audio_rtp_record_handler.cpp +++ b/daemon/src/audio/audiortp/audio_rtp_record_handler.cpp @@ -109,6 +109,7 @@ AudioRtpRecord::AudioRtpRecord() : #endif , dtmfPayloadType_(101) // same as Asterisk , dead_(false) + , currentCodecIndex_(0) {} // Call from processData* @@ -121,6 +122,16 @@ bool AudioRtpRecord::isDead() #endif } +sfl::AudioCodec * +AudioRtpRecord::getCurrentCodec() const +{ + if (audioCodecs_.empty() or currentCodecIndex_ >= audioCodecs_.size()) { + ERROR("No codec found"); + return 0; + } + return audioCodecs_[currentCodecIndex_]; +} + void AudioRtpRecord::deleteCodecs() { @@ -129,6 +140,23 @@ AudioRtpRecord::deleteCodecs() audioCodecs_.clear(); } +bool AudioRtpRecord::tryToSwitchPayloadTypes(int newPt) +{ + for (std::vector<AudioCodec *>::iterator i = audioCodecs_.begin(); i != audioCodecs_.end(); ++i) + if (*i and (*i)->getPayloadType() == newPt) { + codecPayloadType_ = (*i)->getPayloadType(); + codecSampleRate_ = (*i)->getClockRate(); + codecFrameSize_ = (*i)->getFrameSize(); + hasDynamicPayloadType_ = (*i)->hasDynamicPayload(); + currentCodecIndex_ = std::distance(audioCodecs_.begin(), i); + DEBUG("Switched payload type to %d", newPt); + return true; + } + + ERROR("Could not switch payload types"); + return false; +} + AudioRtpRecord::~AudioRtpRecord() { dead_ = true; @@ -174,6 +202,8 @@ void AudioRtpRecordHandler::setRtpMedia(const std::vector<AudioCodec*> &audioCod audioRtpRecord_.deleteCodecs(); // Set varios codec info to reduce indirection audioRtpRecord_.audioCodecs_ = audioCodecs; + + audioRtpRecord_.currentCodecIndex_ = 0; audioRtpRecord_.codecPayloadType_ = audioCodecs[0]->getPayloadType(); audioRtpRecord_.codecSampleRate_ = audioCodecs[0]->getClockRate(); audioRtpRecord_.codecFrameSize_ = audioCodecs[0]->getFrameSize(); @@ -273,13 +303,9 @@ int AudioRtpRecordHandler::processDataEncode() { ost::MutexLock lock(audioRtpRecord_.audioCodecMutex_); - if (audioRtpRecord_.audioCodecs_.empty()) { - ERROR("Audio codecs already destroyed"); - return 0; - } - RETURN_IF_NULL(audioRtpRecord_.audioCodecs_[0], 0, "Audio codec already destroyed"); + RETURN_IF_NULL(audioRtpRecord_.getCurrentCodec(), 0, "Audio codec already destroyed"); unsigned char *micDataEncoded = audioRtpRecord_.encodedData_.data(); - return audioRtpRecord_.audioCodecs_[0]->encode(micDataEncoded, out, getCodecFrameSize()); + return audioRtpRecord_.getCurrentCodec()->encode(micDataEncoded, out, getCodecFrameSize()); } } #undef RETURN_IF_NULL @@ -291,27 +317,24 @@ void AudioRtpRecordHandler::processDataDecode(unsigned char *spkrData, size_t si if (audioRtpRecord_.isDead()) return; if (audioRtpRecord_.codecPayloadType_ != payloadType) { - if (!warningInterval_) { - warningInterval_ = 250; - WARN("Invalid payload type %d, expected %d", payloadType, audioRtpRecord_.codecPayloadType_); - WARN("We have %u codecs total", audioRtpRecord_.audioCodecs_.size()); + const bool switched = audioRtpRecord_.tryToSwitchPayloadTypes(payloadType); + if (not switched) { + if (!warningInterval_) { + warningInterval_ = 250; + WARN("Invalid payload type %d, expected %d", payloadType, audioRtpRecord_.codecPayloadType_); + } + warningInterval_--; + return; } - warningInterval_--; - return; } int inSamples = 0; size = std::min(size, audioRtpRecord_.decData_.size()); SFLDataFormat *spkrDataDecoded = audioRtpRecord_.decData_.data(); { - ost::MutexLock lock(audioRtpRecord_.audioCodecMutex_); - if (audioRtpRecord_.audioCodecs_.empty()) { - ERROR("Audio codecs already destroyed"); - return; - } - RETURN_IF_NULL(audioRtpRecord_.audioCodecs_[0], "Audio codecs already destroyed"); + RETURN_IF_NULL(audioRtpRecord_.getCurrentCodec(), "Audio codecs already destroyed"); // Return the size of data in samples - inSamples = audioRtpRecord_.audioCodecs_[0]->decode(spkrDataDecoded, spkrData, size); + inSamples = audioRtpRecord_.getCurrentCodec()->decode(spkrDataDecoded, spkrData, size); } #if HAVE_SPEEXDSP @@ -354,6 +377,7 @@ void AudioRtpRecord::fadeInDecodedData(size_t size) if (fadeFactor_ >= 1.0 or size > decData_.size()) return; + // FIXME: this takes a lot more cycles than a plain old loop std::transform(decData_.begin(), decData_.begin() + size, decData_.begin(), std::bind1st(std::multiplies<double>(), fadeFactor_)); @@ -361,4 +385,23 @@ void AudioRtpRecord::fadeInDecodedData(size_t size) const double FADEIN_STEP_SIZE = 4.0; fadeFactor_ *= FADEIN_STEP_SIZE; } + +bool +AudioRtpRecordHandler::codecsDiffer(const std::vector<AudioCodec*> &codecs) const +{ + const std::vector<AudioCodec*> ¤t = audioRtpRecord_.audioCodecs_; + if (codecs.size() != current.size()) + return true; + for (std::vector<AudioCodec*>::const_iterator i = codecs.begin(); i != codecs.end(); ++i) { + if (*i) { + bool matched = false; + for (std::vector<AudioCodec*>::const_iterator j = current.begin(); !matched and j != current.end(); ++j) + matched = (*i)->getPayloadType() == (*j)->getPayloadType(); + if (not matched) + return true; + } + } + return false; +} + } diff --git a/daemon/src/audio/audiortp/audio_rtp_record_handler.h b/daemon/src/audio/audiortp/audio_rtp_record_handler.h index 2f35664bb2993d3e566f78b9216d9c948e4434d6..98695bf922ab65affea95c9abc776f2f4dceedb4 100644 --- a/daemon/src/audio/audiortp/audio_rtp_record_handler.h +++ b/daemon/src/audio/audiortp/audio_rtp_record_handler.h @@ -73,6 +73,8 @@ class AudioRtpRecord { AudioRtpRecord(); ~AudioRtpRecord(); void deleteCodecs(); + bool tryToSwitchPayloadTypes(int newPt); + sfl::AudioCodec* getCurrentCodec() const; std::string callId_; int codecSampleRate_; std::list<DTMFEvent> dtmfQueue_; @@ -112,6 +114,7 @@ class AudioRtpRecord { #else ucommon::atomic::counter dead_; #endif + size_t currentCodecIndex_; }; @@ -180,6 +183,7 @@ class AudioRtpRecordHandler { void putDtmfEvent(char digit); protected: + bool codecsDiffer(const std::vector<AudioCodec*> &codecs) const; AudioRtpRecord audioRtpRecord_; private: diff --git a/daemon/src/audio/audiortp/audio_rtp_session.cpp b/daemon/src/audio/audiortp/audio_rtp_session.cpp index a33811f491937d5e0272e172c7e0d1e371316db2..e7eb4fbbaeb3d32265e3e0add7c3ec44fe4cfdf9 100644 --- a/daemon/src/audio/audiortp/audio_rtp_session.cpp +++ b/daemon/src/audio/audiortp/audio_rtp_session.cpp @@ -65,7 +65,8 @@ void AudioRtpSession::updateSessionMedia(const std::vector<AudioCodec*> &audioCo { int lastSamplingRate = audioRtpRecord_.codecSampleRate_; - setSessionMedia(audioCodecs); + if (codecsDiffer(audioCodecs)) + setSessionMedia(audioCodecs); Manager::instance().audioSamplingRateChanged(audioRtpRecord_.codecSampleRate_); diff --git a/daemon/src/iax/iaxvoiplink.cpp b/daemon/src/iax/iaxvoiplink.cpp index ac846e098013c353c2a5619ef58aff8d3c753181..aaf1333cf26eb123af2f7ba986d1cb7af55a72fe 100644 --- a/daemon/src/iax/iaxvoiplink.cpp +++ b/daemon/src/iax/iaxvoiplink.cpp @@ -475,7 +475,7 @@ IAXVoIPLink::getCurrentVideoCodecName(Call * /*call*/) const } std::string -IAXVoIPLink::getCurrentAudioCodecName(Call *c) const +IAXVoIPLink::getCurrentAudioCodecNames(Call *c) const { IAXCall *call = static_cast<IAXCall*>(c); sfl::Codec *audioCodec = Manager::instance().audioCodecFactory.getCodec(call->getAudioCodec()); diff --git a/daemon/src/iax/iaxvoiplink.h b/daemon/src/iax/iaxvoiplink.h index 3c7bf7efb19468219bf93b48f3f933272bccca72..d7b53d9f9c9de019df9355882ade5aa0d643acbc 100644 --- a/daemon/src/iax/iaxvoiplink.h +++ b/daemon/src/iax/iaxvoiplink.h @@ -193,7 +193,7 @@ class IAXVoIPLink : public VoIPLink { * @param id The call identifier */ virtual std::string getCurrentVideoCodecName(Call *c) const; - virtual std::string getCurrentAudioCodecName(Call *c) const; + virtual std::string getCurrentAudioCodecNames(Call *c) const; private: NON_COPYABLE(IAXVoIPLink); diff --git a/daemon/src/managerimpl.cpp b/daemon/src/managerimpl.cpp index 8917a13c688f8c44d0e6f14ce6b27211b4a8b1c8..94a450581e735a8674ab3081aa1502ef5ae19b25 100644 --- a/daemon/src/managerimpl.cpp +++ b/daemon/src/managerimpl.cpp @@ -1874,7 +1874,7 @@ std::string ManagerImpl::getCurrentAudioCodecName(const std::string& id) Call::CallState state = call->getState(); if (state == Call::ACTIVE or state == Call::CONFERENCING) - codecName = link->getCurrentAudioCodecName(call); + codecName = link->getCurrentAudioCodecNames(call); } return codecName; diff --git a/daemon/src/sip/sdp.cpp b/daemon/src/sip/sdp.cpp index e868ea4dc6c30794d0935ad3e79216264ad025ce..737e93ce9a847fdc7d08f3a9344ab7188429efce 100644 --- a/daemon/src/sip/sdp.cpp +++ b/daemon/src/sip/sdp.cpp @@ -71,15 +71,23 @@ Sdp::Sdp(pj_pool_t *pool) , telephoneEventPayload_(101) // same as asterisk {} +namespace { + bool hasPayload(const std::vector<sfl::AudioCodec*> &codecs, int pt) + { + for (std::vector<sfl::AudioCodec*>::const_iterator i = codecs.begin(); i != codecs.end(); ++i) + if (*i and (*i)->getPayloadType() == pt) + return true; + return false; + } +} + + void Sdp::setActiveLocalSdpSession(const pjmedia_sdp_session *sdp) { activeLocalSession_ = (pjmedia_sdp_session*) sdp; - if (activeLocalSession_->media_count < 1) - return; - - for (unsigned media = 0; media < activeLocalSession_->media_count; ++media) { - pjmedia_sdp_media *current = activeLocalSession_->media[media]; + for (unsigned i = 0; i < activeLocalSession_->media_count; ++i) { + pjmedia_sdp_media *current = activeLocalSession_->media[i]; for (unsigned fmt = 0; fmt < current->desc.fmt_count; ++fmt) { static const pj_str_t STR_RTPMAP = { (char*) "rtpmap", 6 }; @@ -93,24 +101,22 @@ void Sdp::setActiveLocalSdpSession(const pjmedia_sdp_session *sdp) pjmedia_sdp_rtpmap *rtpmap; pjmedia_sdp_attr_to_rtpmap(memPool_, attribute, &rtpmap); - string type(current->desc.media.ptr, current->desc.media.slen); - if (type == "audio") { + if (!pj_stricmp2(¤t->desc.media, "audio")) { const int pt = pj_strtoul(&rtpmap->pt); - sfl::Codec *codec = Manager::instance().audioCodecFactory.getCodec(pt); - if (codec) - sessionAudioMedia_.push_back(codec); - else { - DEBUG("Could not get codec for payload type %lu", pt); - break; + if (not hasPayload(sessionAudioMedia_, pt)) { + sfl::AudioCodec *codec = Manager::instance().audioCodecFactory.getCodec(pt); + if (codec) + sessionAudioMedia_.push_back(codec); + else + ERROR("Could not get codec for payload type %lu", pt); } - } else if (type == "video") + } else if (!pj_stricmp2(¤t->desc.media, "video")) sessionVideoMedia_.push_back(string(rtpmap->enc_name.ptr, rtpmap->enc_name.slen)); } } } - void Sdp::setActiveRemoteSdpSession(const pjmedia_sdp_session *sdp) { activeRemoteSession_ = (pjmedia_sdp_session*) sdp; @@ -121,22 +127,44 @@ void Sdp::setActiveRemoteSdpSession(const pjmedia_sdp_session *sdp) } for (unsigned i = 0; i < sdp->media_count; i++) { - if (pj_stricmp2(&sdp->media[i]->desc.media, "audio") == 0) { - pjmedia_sdp_media *r_media = sdp->media[i]; + pjmedia_sdp_media *r_media = sdp->media[i]; + if (!pj_stricmp2(&r_media->desc.media, "audio")) { static const pj_str_t STR_TELEPHONE_EVENT = { (char*) "telephone-event", 15}; - pjmedia_sdp_attr *attribute = pjmedia_sdp_attr_find(r_media->attr_count, r_media->attr, &STR_TELEPHONE_EVENT, NULL); + pjmedia_sdp_attr *telephoneEvent = pjmedia_sdp_attr_find(r_media->attr_count, r_media->attr, &STR_TELEPHONE_EVENT, NULL); - if (attribute != NULL) { + if (telephoneEvent != NULL) { pjmedia_sdp_rtpmap *rtpmap; - pjmedia_sdp_attr_to_rtpmap(memPool_, attribute, &rtpmap); + pjmedia_sdp_attr_to_rtpmap(memPool_, telephoneEvent, &rtpmap); telephoneEventPayload_ = pj_strtoul(&rtpmap->pt); } - return; + // add audio codecs from remote as needed + for (unsigned fmt = 0; fmt < r_media->desc.fmt_count; ++fmt) { + + static const pj_str_t STR_RTPMAP = { (char*) "rtpmap", 6 }; + pjmedia_sdp_attr *rtpMapAttr = pjmedia_sdp_media_find_attr(r_media, &STR_RTPMAP, NULL); + + if (!rtpMapAttr) { + ERROR("Could not find rtpmap attribute"); + continue; + } + + pjmedia_sdp_rtpmap *rtpmap; + pjmedia_sdp_attr_to_rtpmap(memPool_, rtpMapAttr, &rtpmap); + + const int pt = pj_strtoul(&rtpmap->pt); + if (not hasPayload(sessionAudioMedia_, pt)) { + sfl::AudioCodec *codec = Manager::instance().audioCodecFactory.getCodec(pt); + if (codec) { + DEBUG("Adding codec with new payload type %d", pt); + sessionAudioMedia_.push_back(codec); + } else + DEBUG("Could not get codec for payload type %lu", pt); + } + } } } - - ERROR("Could not found dtmf event from remote sdp"); + DEBUG("Using %u audio codecs total", sessionAudioMedia_.size()); } string Sdp::getSessionVideoCodec() const @@ -148,20 +176,23 @@ string Sdp::getSessionVideoCodec() const return sessionVideoMedia_[0]; } -string Sdp::getAudioCodecName() const +string Sdp::getAudioCodecNames() const { - sfl::AudioCodec *codec = getSessionAudioMedia(); - return codec ? codec->getMimeSubtype() : ""; + std::string result; + char sep = ' '; + for (std::vector<sfl::AudioCodec*>::const_iterator i = sessionAudioMedia_.begin(); + i != sessionAudioMedia_.end(); ++i) { + if (i == sessionAudioMedia_.end() - 1) + sep = '\0'; + if (*i) + result += (*i)->getMimeSubtype() + sep; + } + return result; } -sfl::AudioCodec* Sdp::getSessionAudioMedia() const +void Sdp::getSessionAudioMedia(std::vector<sfl::AudioCodec*> &codecs) const { - if (sessionAudioMedia_.empty()) { - ERROR("No codec description for this media"); - return 0; - } - - return static_cast<sfl::AudioCodec *>(sessionAudioMedia_[0]); + codecs = sessionAudioMedia_; } diff --git a/daemon/src/sip/sdp.h b/daemon/src/sip/sdp.h index 90eef19a4ba5fd718a7e83787a92dfe7cbe50180..2fefc3f876ab147de4fa851e20f4d5a4d41eaad7 100644 --- a/daemon/src/sip/sdp.h +++ b/daemon/src/sip/sdp.h @@ -239,9 +239,9 @@ class Sdp { void setMediaTransportInfoFromRemoteSdp(); - std::string getAudioCodecName() const; + std::string getAudioCodecNames() const; std::string getSessionVideoCodec() const; - sfl::AudioCodec* getSessionAudioMedia() const; + void getSessionAudioMedia(std::vector<sfl::AudioCodec*> &) const; // Sets @param settings with appropriate values and returns true if // we are sending video, false otherwise bool getOutgoingVideoSettings(std::map<std::string, std::string> &settings) const; @@ -298,7 +298,7 @@ class Sdp { /** * The codecs that will be used by the session (after the SDP negotiation) */ - std::vector<sfl::Codec *> sessionAudioMedia_; + std::vector<sfl::AudioCodec *> sessionAudioMedia_; std::vector<std::string> sessionVideoMedia_; std::string localIpAddr_; diff --git a/daemon/src/sip/sipcall.cpp b/daemon/src/sip/sipcall.cpp index 1987fe677dd740eebc5c84289d13990c8ee9a962..f91a00af429ca5774a300594e371ce4aad36c342 100644 --- a/daemon/src/sip/sipcall.cpp +++ b/daemon/src/sip/sipcall.cpp @@ -89,7 +89,7 @@ std::map<std::string, std::string> SIPCall::createHistoryEntry() const { std::map<std::string, std::string> entry(Call::createHistoryEntry()); - entry[HistoryItem::AUDIO_CODEC_KEY] = local_sdp_->getAudioCodecName(); + entry[HistoryItem::AUDIO_CODEC_KEY] = local_sdp_->getAudioCodecNames(); #ifdef SFL_VIDEO entry[HistoryItem::VIDEO_CODEC_KEY] = local_sdp_->getSessionVideoCodec(); #endif diff --git a/daemon/src/sip/sipvoiplink.cpp b/daemon/src/sip/sipvoiplink.cpp index 72f9fc4aa0024e01bf58ba5bbd03e9ae64d7db9e..2c1182b2131396ae06ef66aed8f4654d53b299d8 100644 --- a/daemon/src/sip/sipvoiplink.cpp +++ b/daemon/src/sip/sipvoiplink.cpp @@ -1030,19 +1030,23 @@ SIPVoIPLink::offhold(const std::string& id) throw VoipLinkException("Could not find sdp session"); try { - int pl = PAYLOAD_CODEC_ULAW; - sfl::AudioCodec *sessionMedia = sdpSession->getSessionAudioMedia(); - if (sessionMedia) - pl = sessionMedia->getPayloadType(); + std::vector<sfl::AudioCodec*> sessionMedia; + sdpSession->getSessionAudioMedia(sessionMedia); + std::vector<sfl::AudioCodec*> audioCodecs; + for (std::vector<sfl::AudioCodec*>::const_iterator i = sessionMedia.begin(); + i != sessionMedia.end(); ++i) { - // Create a new instance for this codec - sfl::AudioCodec* ac = Manager::instance().audioCodecFactory.instantiateCodec(pl); + if (!*i) + continue; - if (ac == NULL) - throw VoipLinkException("Could not instantiate codec"); + // Create a new instance for this codec + sfl::AudioCodec* ac = Manager::instance().audioCodecFactory.instantiateCodec((*i)->getPayloadType()); - std::vector<sfl::AudioCodec *> audioCodecs; - audioCodecs.push_back(ac); + if (ac == NULL) + throw VoipLinkException("Could not instantiate codec"); + + audioCodecs.push_back(ac); + } call->getAudioRtp().initConfig(); call->getAudioRtp().initSession(); @@ -1274,9 +1278,9 @@ SIPVoIPLink::getCurrentVideoCodecName(Call *call) const } std::string -SIPVoIPLink::getCurrentAudioCodecName(Call *call) const +SIPVoIPLink::getCurrentAudioCodecNames(Call *call) const { - return static_cast<SIPCall*>(call)->getLocalSDP()->getAudioCodecName(); + return static_cast<SIPCall*>(call)->getLocalSDP()->getAudioCodecNames(); } /* Only use this macro with string literals or character arrays, will not work @@ -1751,8 +1755,9 @@ void sdp_media_update_cb(pjsip_inv_session *inv, pj_status_t status) } #endif // HAVE_SDES - sfl::AudioCodec *sessionMedia = sdpSession->getSessionAudioMedia(); - if (!sessionMedia) + std::vector<sfl::AudioCodec*> sessionMedia; + sdpSession->getSessionAudioMedia(sessionMedia); + if (sessionMedia.empty()) return; try { @@ -1760,16 +1765,19 @@ void sdp_media_update_cb(pjsip_inv_session *inv, pj_status_t status) Manager::instance().getAudioDriver()->startStream(); Manager::instance().audioLayerMutexUnlock(); - int pl = sessionMedia->getPayloadType(); + std::vector<AudioCodec*> audioCodecs; + for (std::vector<sfl::AudioCodec*>::const_iterator i = sessionMedia.begin(); i != sessionMedia.end(); ++i) { + if (!*i) + continue; + const int pl = (*i)->getPayloadType(); - if (pl != call->getAudioRtp().getSessionMedia()) { sfl::AudioCodec *ac = Manager::instance().audioCodecFactory.instantiateCodec(pl); if (!ac) throw std::runtime_error("Could not instantiate codec"); - std::vector<AudioCodec*> audioCodecs; audioCodecs.push_back(ac); - call->getAudioRtp().updateSessionMedia(audioCodecs); } + if (not audioCodecs.empty()) + call->getAudioRtp().updateSessionMedia(audioCodecs); } catch (const SdpException &e) { ERROR("%s", e.what()); } catch (const std::exception &rtpException) { diff --git a/daemon/src/sip/sipvoiplink.h b/daemon/src/sip/sipvoiplink.h index 5dfad36f4c91ce08b4dcbdeb2818f348c9d0fd33..d525a1c04c0b18ce967df003231884f93d1d7f3a 100644 --- a/daemon/src/sip/sipvoiplink.h +++ b/daemon/src/sip/sipvoiplink.h @@ -252,7 +252,7 @@ class SIPVoIPLink : public VoIPLink { * @param c The call identifier */ std::string getCurrentVideoCodecName(Call *c) const; - std::string getCurrentAudioCodecName(Call *c) const; + std::string getCurrentAudioCodecNames(Call *c) const; /** * Retrive useragent name from account diff --git a/daemon/src/voiplink.cpp b/daemon/src/voiplink.cpp index 75f468ed1d95c9cd04d7044bf7769454c8f51f4a..d6afe4954bca79dd66918f722eb7c584f04aa4c9 100644 --- a/daemon/src/voiplink.cpp +++ b/daemon/src/voiplink.cpp @@ -32,6 +32,7 @@ */ #include "voiplink.h" +#include "account.h" VoIPLink::VoIPLink() : handlingEvents_(false) {} diff --git a/daemon/src/voiplink.h b/daemon/src/voiplink.h index 4cc8590b45d141113ef309ec39448d215e970eee..841eb7679b228c165e5652536fb0487638365118 100644 --- a/daemon/src/voiplink.h +++ b/daemon/src/voiplink.h @@ -147,7 +147,7 @@ class VoIPLink { * @param call The call */ virtual std::string getCurrentVideoCodecName(Call *call) const = 0; - virtual std::string getCurrentAudioCodecName(Call *call) const = 0; + virtual std::string getCurrentAudioCodecNames(Call *call) const = 0; /** * Send a message to a call identified by its callid