diff --git a/core/AmB2ABSession.cpp b/core/AmB2ABSession.cpp new file mode 100644 index 00000000..290835c4 --- /dev/null +++ b/core/AmB2ABSession.cpp @@ -0,0 +1,367 @@ +/* + * $Id: AmB2ABSession.cpp 145 2006-11-26 00:01:18Z sayer $ + * + * Copyright (C) 2006-2007 iptego GmbH + * + * This file is part of sems, a free SIP media server. + * + * sems 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. + * + * For a license to use the sems software under conditions + * other than those described here, or to purchase support for this + * software, please contact iptel.org by e-mail at the following addresses: + * info@iptel.org + * + * sems 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 "AmB2ABSession.h" +#include "AmSessionContainer.h" +#include "AmConfig.h" +#include "AmMediaProcessor.h" + +#include + +AmB2ABSession::AmB2ABSession() + : AmSession(), + connector(NULL) +{ +} + +AmB2ABSession::AmB2ABSession(const string& other_local_tag) + : other_id(other_local_tag), + AmSession() +{} + + +AmB2ABSession::~AmB2ABSession() +{ +} + +void AmB2ABSession::clear_other() +{ +#if __GNUC__ < 3 + string cleared (""); + other_id.assign (cleared, 0, 0); +#else + other_id.clear(); +#endif +} + +void AmB2ABSession::process(AmEvent* event) +{ + B2ABEvent* b2b_e = dynamic_cast(event); + if(b2b_e){ + onB2ABEvent(b2b_e); + return; + } + + AmSession::process(event); +} + +void AmB2ABSession::onB2ABEvent(B2ABEvent* ev) +{ + switch(ev->event_id){ + + case B2ABTerminateLeg: + terminateLeg(); + break; + } +} + +void AmB2ABSession::relayEvent(AmEvent* ev) +{ + DBG("AmB2ABSession::relayEvent: id=%s\n", + other_id.c_str()); + + if(!other_id.empty()) + AmSessionContainer::instance()->postEvent(other_id,ev); +} + +void AmB2ABSession::releaseAudio() { + if (connector) + connector->disconnectSession(this); + + connector=NULL; +} + +void AmB2ABSession::onBye(const AmSipRequest& req) { + terminateOtherLeg(); + releaseAudio(); + setStopped(); +} + +void AmB2ABSession::terminateLeg() +{ + dlg.bye(); + releaseAudio(); + setStopped(); +} + +void AmB2ABSession::terminateOtherLeg() +{ + relayEvent(new B2ABEvent(B2ABTerminateLeg)); + clear_other(); +} + +AmB2ABCallerSession::AmB2ABCallerSession() + : AmB2ABSession(), + callee_status(None) +{ +} +AmB2ABCallerSession::~AmB2ABCallerSession() +{ } + +void AmB2ABCallerSession::terminateOtherLeg() +{ + if (callee_status != None) + AmB2ABSession::terminateOtherLeg(); + callee_status = None; +} + +void AmB2ABCallerSession::onB2ABEvent(B2ABEvent* ev) +{ + + switch(ev->event_id) { + case B2ABConnectAudio: { + callee_status = Connected; + + DBG("ConnectAudio event received from other leg\n"); + B2ABConnectAudioEvent* ca = dynamic_cast(ev); + assert(ca); + + connector = ca->connector; + connector->connectSession(this, ca->first); + AmMediaProcessor::instance()->addSession(this, callgroup); + + return; + } break; + + case B2ABConnectOtherLegException: + case B2ABConnectOtherLegFailed: { + DBG("looks like callee leg could not be created. terminating our leg.\n"); + terminateLeg(); + callee_status = None; + return; + } break; + + case B2ABOtherLegRinging: { + DBG("callee_status set to Ringing.\n"); + callee_status = Ringing; + return; + } break; + + } + + AmB2ABSession::onB2ABEvent(ev); +} + +void AmB2ABCallerSession::connectCallee(const string& remote_party, + const string& remote_uri, + const string& local_party, + const string& local_uri) +{ + if(callee_status != None) + terminateOtherLeg(); + + B2ABConnectLegEvent* ev = new B2ABConnectLegEvent(remote_party,remote_uri, + local_party,local_uri, + getLocalTag()); + + relayEvent(ev); + callee_status = NoReply; +} + +void AmB2ABCallerSession::relayEvent(AmEvent* ev) +{ + if(other_id.empty()){ + B2ABConnectLegEvent* co_ev = dynamic_cast(ev); + if( co_ev ) { + setupCalleeSession(createCalleeSession(), co_ev); + } + } + + AmB2ABSession::relayEvent(ev); +} + +void AmB2ABCallerSession::setupCalleeSession(AmB2ABCalleeSession* callee_session, + B2ABConnectLegEvent* ev) { + other_id = AmSession::getNewId(); + + AmSipDialog& callee_dlg = callee_session->dlg; + callee_dlg.callid = AmSession::getNewId() + "@" + AmConfig::LocalIP; + callee_dlg.local_tag = other_id; + + callee_session->start(); + + AmSessionContainer* sess_cont = AmSessionContainer::instance(); + sess_cont->addSession(other_id,callee_session); +} + +AmB2ABCalleeSession* AmB2ABCallerSession::createCalleeSession() +{ + return new AmB2ABCalleeSession(getLocalTag()); +} + +AmB2ABCalleeSession::AmB2ABCalleeSession(const string& other_local_tag) + : AmB2ABSession(other_local_tag) +{ } + +AmB2ABCalleeSession::~AmB2ABCalleeSession() +{ } + +void AmB2ABCalleeSession::onB2ABEvent(B2ABEvent* ev) +{ + if(ev->event_id == B2ABConnectLeg){ + + try { + B2ABConnectLegEvent* co_ev = dynamic_cast(ev); + assert(co_ev); + + dlg.local_party = co_ev->local_party; + dlg.local_uri = co_ev->local_uri; + + dlg.remote_party = co_ev->remote_party; + dlg.remote_uri = co_ev->remote_uri; + + setCallgroup(co_ev->callgroup); + + setNegotiateOnReply(true); + sendInvite(); + return; + } + catch(const AmSession::Exception& e){ + ERROR("%i %s\n",e.code,e.reason.c_str()); + relayEvent(new B2ABConnectOtherLegExceptionEvent(e.code,e.reason)); + setStopped(); + } + catch(const string& err){ + ERROR("startSession: %s\n",err.c_str()); + relayEvent(new B2ABConnectOtherLegExceptionEvent(500,err)); + setStopped(); + } + catch(...){ + ERROR("unexpected exception\n"); + relayEvent(new B2ABConnectOtherLegExceptionEvent(500,"unexpected exception")); + setStopped(); + } + } + + AmB2ABSession::onB2ABEvent(ev); +} + +void AmB2ABCalleeSession::onSessionStart(const AmSipReply& rep) { + DBG("onSessionStart of callee session\n"); + // connect our audio + connector = new AmSessionAudioConnector(this, true); + // acc to mediaprocessor + AmMediaProcessor::instance()->addSession(this, callgroup); + relayEvent(new B2ABConnectAudioEvent(connector, false)); +} + +void AmB2ABCalleeSession::reconnectAudio() { + if (connector) { + connector->connectSession(this, true); + AmMediaProcessor::instance()->addSession(this, callgroup); + } else { + ERROR("can not connect audio of not connected session.\n"); + } +} + +void AmB2ABCallerSession::reconnectAudio() { + if (connector) { + connector->connectSession(this, false); + AmMediaProcessor::instance()->addSession(this, callgroup); + } else { + ERROR("can not connect audio of not connected session.\n"); + } +} + +void AmB2ABCalleeSession::onSipReply(const AmSipReply& rep) { + int status_before = dlg.getStatus(); + AmB2ABSession::onSipReply(rep); + int status = dlg.getStatus(); + + if ((status_before == AmSipDialog::Pending)&& + (status == AmSipDialog::Disconnected)) { + + DBG("callee session creation failed. notifying callersession.\n"); + relayEvent(new B2ABConnectOtherLegFailedEvent(rep.code, rep.reason)); + + } else if ((status == AmSipDialog::Pending) && (rep.code == 180)) { + relayEvent(new B2ABOtherLegRingingEvent()); + } +} + + +// ----------------------- SessionAudioConnector ----------------- +AmSessionAudioConnector::AmSessionAudioConnector(AmSession* sess, bool first) { + connectSession(sess, first); +} + +AmSessionAudioConnector::~AmSessionAudioConnector() { +} + +void AmSessionAudioConnector::connectSession(AmSession* sess, bool first) { + if (first) { + tag_sess1 = sess->getLocalTag(); + sess->setInOut(&audio_connectors[0],&audio_connectors[1]); + } else { + tag_sess2 = sess->getLocalTag(); + sess->setInOut(&audio_connectors[1],&audio_connectors[0]); + } +} + +void AmSessionAudioConnector::disconnectSession(AmSession* sess) { + if (tag_sess1 == sess->getLocalTag()) { + tag_sess1.clear(); + sess->setInOut(NULL, NULL); + if ((!tag_sess1.length())&& (!tag_sess2.length())) { + delete this; // both are disconnected -> destroy self + } + } else if (tag_sess2 == sess->getLocalTag()) { + tag_sess2.clear(); + sess->setInOut(NULL, NULL); + if ((!tag_sess1.length())&& (!tag_sess2.length())){ + delete this; // both are disconnected -> destroy self + } + } else { + ERROR("disconnecting from wrong AmSessionAudioConnector\n"); + } +} + +// ----------------------- AudioDelayBridge ----------------- +/** BRIDGE_DELAY is needed because of possible different packet sizes */ +#define BRIDGE_DELAY 320 // 30ms + +/* AudioBridge */ +AmAudioDelayBridge::AmAudioDelayBridge() + : AmAudio(new AmAudioSimpleFormat(CODEC_PCM16)) +{ + sarr.clear_all(); +} + +AmAudioDelayBridge::~AmAudioDelayBridge() { +} + +int AmAudioDelayBridge::write(unsigned int user_ts, unsigned int size) { + // DBG("bridge write %u - this = %lu\n", user_ts + BRIDGE_DELAY, (unsigned long) this); + sarr.write(user_ts + BRIDGE_DELAY, (short*) ((unsigned char*) samples), size >> 1); + return size; +} + +int AmAudioDelayBridge::read(unsigned int user_ts, unsigned int size) { + // DBG("bridge read %u - this = %lu\n", user_ts, (unsigned long) this); + sarr.read(user_ts, (short*) ((unsigned char*) samples), size >> 1); + return size; +} diff --git a/core/AmB2ABSession.h b/core/AmB2ABSession.h new file mode 100644 index 00000000..1ee08bf6 --- /dev/null +++ b/core/AmB2ABSession.h @@ -0,0 +1,272 @@ +/* + * $Id: AmB2ABSession.h 145 2006-11-26 00:01:18Z sayer $ + * + * Copyright (C) 2006-2007 iptego GmbH + * + * This file is part of sems, a free SIP media server. + * + * sems 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. + * + * For a license to use the sems software under conditions + * other than those described here, or to purchase support for this + * software, please contact iptel.org by e-mail at the following addresses: + * info@iptel.org + * + * sems 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 + */ +#ifndef B2ABSession_H +#define B2ABSession_H + +#include "AmSession.h" +#include "AmSipDialog.h" +#include "AmAdvancedAudio.h" +#include "SampleArray.h" +#include +using std::string; + +class AmAudioDelayBridge; +class AmSessionAudioConnector; + +enum { B2ABTerminateLeg, + B2ABConnectLeg, + B2ABConnectAudio, + B2ABConnectOtherLegFailed, + B2ABConnectOtherLegException, + B2ABOtherLegRinging +}; + +/** \brief base class for event in B2AB session */ +struct B2ABEvent: public AmEvent +{ + B2ABEvent(int ev_id) + : AmEvent(ev_id) + {} +}; + +struct B2ABOtherLegRingingEvent: public B2ABEvent +{ + B2ABOtherLegRingingEvent() + : B2ABEvent(B2ABOtherLegRinging) + {} +}; + +/** \brief trigger connecting the audio in B2AB session */ +struct B2ABConnectAudioEvent: public B2ABEvent +{ + AmSessionAudioConnector* connector; + bool first; + + B2ABConnectAudioEvent(AmSessionAudioConnector* connector, + bool first) + : B2ABEvent(B2ABConnectAudio), + connector(connector), + first(first) + {} +}; + +/** \brief trigger connecting the callee leg in B2AB session */ +struct B2ABConnectLegEvent: public B2ABEvent +{ + string remote_party; + string remote_uri; + string local_party; + string local_uri; + string callgroup; + + B2ABConnectLegEvent(const string& remote_party, + const string& remote_uri, + const string& local_party, + const string& local_uri, + const string& callgroup) + : B2ABEvent(B2ABConnectLeg), + remote_party(remote_party), + remote_uri(remote_uri), + local_party(local_party), + local_uri(local_uri), + callgroup(callgroup) + {} +}; + +struct B2ABConnectOtherLegExceptionEvent: public B2ABEvent { + + unsigned int code; + string reason; + + B2ABConnectOtherLegExceptionEvent(unsigned int code, + const string& reason) + : B2ABEvent(B2ABConnectOtherLegException), + code(code), reason(reason) + {} +}; + +struct B2ABConnectOtherLegFailedEvent: public B2ABEvent { + + unsigned int code; + string reason; + + B2ABConnectOtherLegFailedEvent(unsigned int code, + const string& reason) + : B2ABEvent(B2ABConnectOtherLegFailed), + code(code), reason(reason) + {} +}; + +/** + * \brief Base class for Sessions in B2ABUA mode. + * + * It has two legs: Callee- and caller-leg. + */ +class AmB2ABSession: public AmSession +{ + + protected: + /** local tag of the other leg */ + string other_id; + + /** Requests received for relaying */ + map recvd_req; + + void clear_other(); + + /** Relay one event to the other side. */ + virtual void relayEvent(AmEvent* ev); + + /** Terminate our leg and forget the other. */ + virtual void terminateLeg(); + + /** Terminate the other leg and forget it.*/ + virtual void terminateOtherLeg(); + + + /** B2ABEvent handler */ + virtual void onB2ABEvent(B2ABEvent* ev); + + /* // Other leg received a BYE */ + /* virtual void onOtherBye(const AmSipRequest& req); */ + + /* /\** INVITE from other leg has been replied *\/ */ + /* virtual void onOtherReply(const AmSipReply& reply); */ + + /** @see AmEventQueue */ + void process(AmEvent* event); + + /** reference to the audio connector - caution: you may not delete this! */ + AmSessionAudioConnector* connector; + + + /** set in&out to NULL and release connector */ + void releaseAudio(); + public: + + AmB2ABSession(); + AmB2ABSession(const string& other_local_tag); + ~AmB2ABSession(); + + void onBye(const AmSipRequest& req); +}; + +class AmB2ABCalleeSession; + +/** \brief Caller leg of a B2AB session */ +class AmB2ABCallerSession: public AmB2ABSession +{ + public: + enum CalleeStatus { + None=0, + NoReply, + Ringing, + Connected + }; + + private: + // Callee Status + CalleeStatus callee_status; + + virtual void setupCalleeSession(AmB2ABCalleeSession* callee_session, + B2ABConnectLegEvent* ev); + + protected: + virtual AmB2ABCalleeSession* createCalleeSession(); + void relayEvent(AmEvent* ev); + + public: + AmB2ABCallerSession(); + ~AmB2ABCallerSession(); + + CalleeStatus getCalleeStatus() { return callee_status; } + + void connectCallee(const string& remote_party, + const string& remote_uri, + const string& local_party, + const string& local_uri); + + void reconnectAudio(); + + // @see AmB2ABSession + void terminateOtherLeg(); + protected: + void onB2ABEvent(B2ABEvent* ev); +}; + +/** \brief Callee leg of a B2AB session */ +class AmB2ABCalleeSession: public AmB2ABSession +{ + public: + AmB2ABCalleeSession(const string& other_local_tag); + ~AmB2ABCalleeSession(); + + void onB2ABEvent(B2ABEvent* ev); + void onSessionStart(const AmSipReply& rep); + void onSipReply(const AmSipReply& rep); + + void reconnectAudio(); +}; + +/** + * \brief \ref AmAudio that connects input and output with delay + * + * AmAudioDelayBridge simply connects input and output + * with a fixed packet size delay. + */ +class AmAudioDelayBridge : public AmAudio { + SampleArrayShort sarr; + public: + AmAudioDelayBridge(); + ~AmAudioDelayBridge(); + protected: + int write(unsigned int user_ts, unsigned int size); + int read(unsigned int user_ts, unsigned int size); +}; + +/** + * \brief connects the audio of two sessions together + */ +class AmSessionAudioConnector { + private: + AmAudioDelayBridge audio_connectors[2]; + int refcnt; + ~AmSessionAudioConnector(); + string tag_sess1; + string tag_sess2; + public: + /** create a connector, connect audio to sess */ + AmSessionAudioConnector(AmSession* sess, bool first); + /** connect audio to sess */ + void connectSession(AmSession* sess, bool first); + /** disconnect session */ + void disconnectSession(AmSession* sess); +}; + + +#endif +