diff --git a/MumbleChannelJoiner.cpp b/MumbleChannelJoiner.cpp index 30ad1b1..4f58a7c 100644 --- a/MumbleChannelJoiner.cpp +++ b/MumbleChannelJoiner.cpp @@ -5,12 +5,20 @@ using namespace std; mumble::MumbleChannelJoiner::MumbleChannelJoiner(std::string channelNameRegex) : channelNameRegex(boost::regex(channelNameRegex)), logger(log4cpp::Category::getInstance("MumbleChannelJoiner")){ + //std::vector *channels = new std::vector(); } +std::vector mumble::MumbleChannelJoiner::channels; + void mumble::MumbleChannelJoiner::checkChannel(std::string channel_name, int channel_id) { boost::smatch s; + ChannelEntry ent; logger.debug("Channel %s available (%d)", channel_name.c_str(), channel_id); + ent.name = channel_name; + ent.id = channel_id; + + channels.push_back(ent); if(boost::regex_match(channel_name, s, channelNameRegex)) { this->channel_id = channel_id; @@ -23,3 +31,22 @@ void mumble::MumbleChannelJoiner::maybeJoinChannel(mumble::MumbleCommunicator *m } } +/* This is a secondary channel-switching object that relys on updates to the + * class variable 'channels' for the channel list from the server. + */ +void mumble::MumbleChannelJoiner::findJoinChannel(mumble::MumbleCommunicator *mc) { + boost::smatch s; + + int found = -1; + + for(std::vector::iterator it = channels.begin(); it != channels.end(); ++it) { + if(boost::regex_match(it->name, s, channelNameRegex)) { + found = it->id; + } + } + + if(found > -1) { + mc->joinChannel(found); + } +} + diff --git a/MumbleChannelJoiner.hpp b/MumbleChannelJoiner.hpp index 356d022..1de5867 100644 --- a/MumbleChannelJoiner.hpp +++ b/MumbleChannelJoiner.hpp @@ -3,21 +3,30 @@ #include #include +#include #include #include #include "MumbleCommunicator.hpp" namespace mumble { class MumbleChannelJoiner : boost::noncopyable { + + struct ChannelEntry { + int id; + std::string name; + }; + public: MumbleChannelJoiner(std::string channelNameRegex); void checkChannel(std::string channel_name, int channel_id); void maybeJoinChannel(mumble::MumbleCommunicator *mc); + void findJoinChannel(mumble::MumbleCommunicator *mc); private: log4cpp::Category &logger; boost::regex channelNameRegex; int channel_id; + static std::vector channels; }; } diff --git a/MumbleCommunicator.cpp b/MumbleCommunicator.cpp index f52511b..ab115d2 100644 --- a/MumbleCommunicator.cpp +++ b/MumbleCommunicator.cpp @@ -9,13 +9,14 @@ namespace mumble { std::shared_ptr mum; MumbleCommunicator *communicator; + // called by Mumlib when receiving audio from mumble server virtual void audio( int target, int sessionId, int sequenceNumber, int16_t *pcm_data, uint32_t pcm_data_size) override { - communicator->onIncomingPcmSamples(sessionId, sequenceNumber, pcm_data, pcm_data_size); + communicator->onIncomingPcmSamples(communicator->callId, sessionId, sequenceNumber, pcm_data, pcm_data_size); } virtual void channelState( @@ -39,6 +40,26 @@ namespace mumble { communicator->onServerSync(); }; + /* + virtual void onUserState( + int32_t session, + int32_t actor, + std::string name, + int32_t user_id, + int32_t channel_id, + int32_t mute, + int32_t deaf, + int32_t suppress, + int32_t self_mute, + int32_t self_deaf, + std::string comment, + int32_t priority_speaker, + int32_t recording + ) override { + communicator->onUserState(); + }; + */ + }; } @@ -60,8 +81,37 @@ void mumble::MumbleCommunicator::connect(MumbleCommunicatorConfig &config) { callback->communicator = this; callback->mum = mum; - mum->connect(config.host, config.port, config.user, config.password); - mum->self_deaf(1); + // IMPORTANT: comment these out when experimenting with onConnect + if ( ! MUM_DELAYED_CONNECT ) { + mum->connect(config.host, config.port, config.user, config.password); + if ( mumbleConf.autodeaf ) { + mum->sendUserState(mumlib::UserState::SELF_DEAF, true); + } + } +} + +void mumble::MumbleCommunicator::onConnect() { + if ( MUM_DELAYED_CONNECT ) { + mum->connect(mumbleConf.host, mumbleConf.port, mumbleConf.user, mumbleConf.password); + } + + if ( mumbleConf.comment.size() > 0 ) { + mum->sendUserState(mumlib::UserState::COMMENT, mumbleConf.comment); + } + if ( mumbleConf.autodeaf ) { + mum->sendUserState(mumlib::UserState::SELF_DEAF, true); + } +} + +void mumble::MumbleCommunicator::onDisconnect() { + if ( MUM_DELAYED_CONNECT ) { + mum->disconnect(); + } else { + } +} + +void mumble::MumbleCommunicator::onCallerAuth() { + //onServerSync(); } void mumble::MumbleCommunicator::sendPcmSamples(int16_t *samples, unsigned int length) { @@ -76,21 +126,45 @@ void mumble::MumbleCommunicator::sendTextMessage(std::string message) { mum->sendTextMessage(message); } +/* +void mumble::MumbleCommunicator::onUserState( + int32_t session, + int32_t actor, + std::string name, + int32_t user_id, + int32_t channel_id, + int32_t mute, + int32_t deaf, + int32_t suppress, + int32_t self_mute, + int32_t self_deaf, + std::string comment, + int32_t priority_speaker, + int32_t recording) { + + logger::notice("Entered onUserState(...)"); + + userState.mute = mute; + userState.deaf = deaf; + userState.suppress = suppress; + userState.self_mute = self_mute; + userState.self_deaf = self_deaf; + userState.priority_speaker = priority_speaker; + userState.recording = recording; +} +*/ + + void mumble::MumbleCommunicator::joinChannel(int channel_id) { mum->joinChannel(channel_id); + if ( mumbleConf.autodeaf ) { - //mum->self_mute(1); - mum->self_deaf(1); + mum->sendUserState(mumlib::UserState::SELF_DEAF, true); } } -void mumble::MumbleCommunicator::mutedeaf(int status) { - if ( mumbleConf.autodeaf ) { - if ( status ) { - mum->self_deaf(status); - } else { - mum->self_mute(status); - } - } + +void mumble::MumbleCommunicator::sendUserState(mumlib::UserState field, bool val) { + mum->sendUserState(field, val); } diff --git a/MumbleCommunicator.hpp b/MumbleCommunicator.hpp index 8fef318..2ea5828 100644 --- a/MumbleCommunicator.hpp +++ b/MumbleCommunicator.hpp @@ -8,6 +8,9 @@ #include #include +// 0 = mumble users connected at start; 1 = connect at dial-in +// TODO: fix mumlib::TransportException when this option is enabled +#define MUM_DELAYED_CONNECT 0 namespace mumble { @@ -26,6 +29,20 @@ namespace mumble { int opusEncoderBitrate; int port = 0; bool autodeaf; + std::string comment; + int max_calls = 1; + std::string authchan; // config.ini: channelAuthExpression + }; + + // This is the subset that is of interest to us + struct MumbleUserState { + int32_t mute; + int32_t deaf; + int32_t suppress; + int32_t self_mute; + int32_t self_deaf; + int32_t priority_speaker; + int32_t recording; }; class MumbleCommunicator : boost::noncopyable { @@ -34,6 +51,10 @@ namespace mumble { boost::asio::io_service &ioService); void connect(MumbleCommunicatorConfig &config); + void onConnect(); + void onDisconnect(); + void onCallerAuth(); + //void onCallerUnauth(); virtual ~MumbleCommunicator(); @@ -41,9 +62,9 @@ namespace mumble { /** * This callback is called when communicator has received samples. - * Arguments: session ID, sequence number, PCM samples, length of samples + * Arguments: call ID, session ID, sequence number, PCM samples, length of samples */ - std::function onIncomingPcmSamples; + std::function onIncomingPcmSamples; /** * This callback is called when a channel state message (e.g. Channel @@ -53,11 +74,17 @@ namespace mumble { std::function onServerSync; + std::function onUserState; + void sendTextMessage(std::string message); void joinChannel(int channel_id); - void mutedeaf(int status); + void sendUserState(mumlib::UserState field, bool val); + + MumbleUserState userState; + + int callId; private: boost::asio::io_service &ioService; diff --git a/PjsuaCommunicator.cpp b/PjsuaCommunicator.cpp index 5d4d56d..abb02c5 100644 --- a/PjsuaCommunicator.cpp +++ b/PjsuaCommunicator.cpp @@ -6,6 +6,8 @@ #include #include +#include "main.hpp" + using namespace std; namespace sip { @@ -38,9 +40,9 @@ namespace sip { class _MumlibAudioMedia : public pj::AudioMedia { public: - _MumlibAudioMedia(sip::PjsuaCommunicator &comm, int frameTimeLength) + _MumlibAudioMedia(int call_id, sip::PjsuaCommunicator &comm, int frameTimeLength) : communicator(comm) { - createMediaPort(frameTimeLength); + createMediaPort(call_id, frameTimeLength); registerMediaPort(&mediaPort); } @@ -62,7 +64,7 @@ namespace sip { return communicator->mediaPortPutFrame(port, frame); } - void createMediaPort(int frameTimeLength) { + void createMediaPort(int call_id, int frameTimeLength) { auto name = pj_str((char *) "MumsiMediaPort"); @@ -88,6 +90,8 @@ namespace sip { } mediaPort.port_data.pdata = &communicator; + // track call id in port_data + mediaPort.port_data.ldata = (long) call_id; mediaPort.get_frame = &callback_getFrame; mediaPort.put_frame = &callback_putFrame; @@ -145,14 +149,19 @@ namespace sip { if (ci.state == PJSIP_INV_STATE_CONFIRMED) { auto msgText = "Incoming call from " + address + "."; + // first, login to Mumble (only matters if MUM_DELAYED_CONNECT) + communicator.calls[ci.id].onConnect(); + pj_thread_sleep(500); // sleep a moment to allow connection to stabilize + communicator.logger.notice(msgText); - communicator.onStateChange(msgText); + communicator.calls[ci.id].onStateChange(msgText); pj_thread_sleep(500); // sleep a moment to allow connection to stabilize this->playAudioFile(communicator.file_welcome); communicator.got_dtmf = ""; + communicator.logger.notice("MYDEBUG: pin length=%d", communicator.caller_pin.length()); /* * if no pin is set, go ahead and turn off mute/deaf * otherwise, wait for pin to be entered @@ -160,11 +169,18 @@ namespace sip { if ( communicator.caller_pin.length() == 0 ) { // No PIN set... enter DTMF root menu and turn off mute/deaf communicator.dtmf_mode = DTMF_MODE_ROOT; - communicator.onMuteDeafChange(0); + // turning off mute automatically turns off deaf + communicator.calls[ci.id].sendUserState(mumlib::UserState::SELF_MUTE, false); + pj_thread_sleep(500); // sleep a moment to allow connection to stabilize + this->playAudioFile(communicator.file_announce_new_caller, true); } else { // PIN set... enter DTMF unauth menu and play PIN prompt message communicator.dtmf_mode = DTMF_MODE_UNAUTH; + communicator.logger.notice("MYDEBUG: call joinDefaultChannel()"); + communicator.calls[ci.id].joinDefaultChannel(); pj_thread_sleep(500); // pause briefly after announcement + communicator.logger.notice("MYDEBUG: call play...()"); + this->playAudioFile(communicator.file_prompt_pin); } @@ -174,16 +190,23 @@ namespace sip { if (not acc.available) { auto msgText = "Call from " + address + " finished."; - communicator.mixer->clear(); + communicator.calls[ci.id].mixer->clear(); communicator.logger.notice(msgText); - communicator.onStateChange(msgText); - communicator.onMuteDeafChange(1); + communicator.calls[ci.id].onStateChange(msgText); + communicator.calls[ci.id].sendUserState(mumlib::UserState::SELF_DEAF, true); + communicator.logger.notice("MYDEBUG: call joinDefaultChannel()"); + communicator.calls[ci.id].joinDefaultChannel(); + + communicator.calls[ci.id].onDisconnect(); acc.available = true; } delete this; + } else { + communicator.logger.notice("MYDEBUG: onCallState() call:%d state:%d", + ci.id, ci.state); } } @@ -197,8 +220,8 @@ namespace sip { if (ci.media[0].status == PJSUA_CALL_MEDIA_ACTIVE) { auto *aud_med = static_cast(getMedia(0)); - communicator.media->startTransmit(*aud_med); - aud_med->startTransmit(*communicator.media); + communicator.calls[ci.id].media->startTransmit(*aud_med); + aud_med->startTransmit(*communicator.calls[ci.id].media); } else if (ci.media[0].status == PJSUA_CALL_MEDIA_NONE) { dynamic_cast<_Account &>(account).available = true; } @@ -244,14 +267,8 @@ namespace sip { pinfo = player.getInfo(); sleeptime = pinfo.sizeBytes / (pinfo.payloadBitsPerSample * 3); - /* - communicator.logger.notice("DEBUG: wavsize=%d pbps=%d bytes=%d samples=%d", - wavsize, pinfo.payloadBitsPerSample, pinfo.sizeBytes, pinfo.sizeSamples); - communicator.logger.notice("WAVE length in ms: %d", sleeptime); - */ - if ( in_chan ) { // choose the target sound output - player.startTransmit(*communicator.media); + player.startTransmit(*communicator.calls[ci.id].media); } else { player.startTransmit(*aud_med); } @@ -259,7 +276,7 @@ namespace sip { pj_thread_sleep(sleeptime); if ( in_chan ) { // choose the target sound output - player.stopTransmit(*communicator.media); + player.stopTransmit(*communicator.calls[ci.id].media); } else { player.stopTransmit(*aud_med); } @@ -277,6 +294,8 @@ namespace sip { // prm.digit.c_str(), getId()); pj::CallOpParam param; + auto ci = getInfo(); + /* * DTMF CALLER MENU */ @@ -295,8 +314,10 @@ namespace sip { if ( communicator.got_dtmf == communicator.caller_pin ) { communicator.logger.notice("Caller entered correct PIN"); communicator.dtmf_mode = DTMF_MODE_ROOT; + communicator.calls[ci.id].joinAuthChannel(); + this->playAudioFile(communicator.file_entering_channel); - communicator.onMuteDeafChange(0); + communicator.calls[ci.id].sendUserState(mumlib::UserState::SELF_MUTE, false); this->playAudioFile(communicator.file_announce_new_caller, true); } else { communicator.logger.notice("Caller entered wrong PIN"); @@ -357,18 +378,20 @@ namespace sip { * User already entered '*'; time to perform action */ switch ( prm.digit[0] ) { - /* case '5': // Mute line - communicator.onMuteChange(1); + communicator.calls[ci.id].sendUserState(mumlib::UserState::SELF_MUTE, true); this->playAudioFile(communicator.file_mute_on); break; case '6': // Un-mute line this->playAudioFile(communicator.file_mute_off); - communicator.onMuteChange(0); + communicator.calls[ci.id].sendUserState(mumlib::UserState::SELF_MUTE, false); + break; + case '0': + // play menu + this->playAudioFile(communicator.file_menu); break; - */ default: communicator.logger.notice("Unsupported DTMF digit '%s' in state STAR", prm.digit.c_str()); } @@ -405,7 +428,13 @@ namespace sip { param.statusCode = PJSIP_SC_OK; available = false; } else { - param.statusCode = PJSIP_SC_BUSY_EVERYWHERE; + /* + * EXPERIMENT WITH MULTI-LINE + */ + communicator.logger.info("MULTI-LINE Incoming call from %s.", uri.c_str()); + param.statusCode = PJSIP_SC_OK; + available = false; + //param.statusCode = PJSIP_SC_BUSY_EVERYWHERE; } call->answer(param); @@ -417,18 +446,19 @@ namespace sip { } } -sip::PjsuaCommunicator::PjsuaCommunicator(IncomingConnectionValidator &validator, int frameTimeLength) +sip::PjsuaCommunicator::PjsuaCommunicator(IncomingConnectionValidator &validator, int frameTimeLength, int maxCalls) : logger(log4cpp::Category::getInstance("SipCommunicator")), pjsuaLogger(log4cpp::Category::getInstance("Pjsua")), uriValidator(validator) { logWriter.reset(new sip::_LogWriter(pjsuaLogger)); + endpoint.libCreate(); pj::EpConfig endpointConfig; endpointConfig.uaConfig.userAgent = "Mumsi Mumble-SIP gateway"; - endpointConfig.uaConfig.maxCalls = 1; + endpointConfig.uaConfig.maxCalls = maxCalls; endpointConfig.logConfig.writer = logWriter.get(); endpointConfig.logConfig.level = 5; @@ -437,11 +467,12 @@ sip::PjsuaCommunicator::PjsuaCommunicator(IncomingConnectionValidator &validator endpoint.libInit(endpointConfig); - pj_caching_pool_init(&cachingPool, &pj_pool_factory_default_policy, 0); - - mixer.reset(new mixer::AudioFramesMixer(cachingPool.factory)); - - media.reset(new _MumlibAudioMedia(*this, frameTimeLength)); + for(int i=0; iaddFrameToBuffer(sessionId, sequenceNumber, samples, length); +void sip::PjsuaCommunicator::sendPcmSamples(int callId, int sessionId, int sequenceNumber, int16_t *samples, unsigned int length) { + calls[callId].mixer->addFrameToBuffer(sessionId, sequenceNumber, samples, length); } pj_status_t sip::PjsuaCommunicator::mediaPortGetFrame(pjmedia_port *port, pjmedia_frame *frame) { @@ -481,7 +512,8 @@ pj_status_t sip::PjsuaCommunicator::mediaPortGetFrame(pjmedia_port *port, pjmedi pj_int16_t *samples = static_cast(frame->buf); pj_size_t count = frame->size / 2 / PJMEDIA_PIA_CCNT(&(port->info)); - const int readSamples = mixer->getMixedSamples(samples, count); + int call_id = (int) port->port_data.ldata; + const int readSamples = calls[call_id].mixer->getMixedSamples(samples, count); if (readSamples < count) { pjsuaLogger.debug("Requested %d samples, available %d, filling remaining with zeros.", @@ -500,9 +532,11 @@ pj_status_t sip::PjsuaCommunicator::mediaPortPutFrame(pjmedia_port *port, pjmedi pj_size_t count = frame->size / 2 / PJMEDIA_PIA_CCNT(&port->info); frame->type = PJMEDIA_FRAME_TYPE_AUDIO; + int call_id = (int) port->port_data.ldata; + if (count > 0) { - pjsuaLogger.debug("Calling onIncomingPcmSamples with %d samples.", count); - onIncomingPcmSamples(samples, count); + pjsuaLogger.debug("Calling onIncomingPcmSamples with %d samples (call_id=%d).", count, call_id); + this->calls[call_id].onIncomingPcmSamples(samples, count); } return PJ_SUCCESS; diff --git a/PjsuaCommunicator.hpp b/PjsuaCommunicator.hpp index bd69979..3ee0113 100644 --- a/PjsuaCommunicator.hpp +++ b/PjsuaCommunicator.hpp @@ -18,6 +18,11 @@ #include #include +// for userState enum +#include + +#include "main.hpp" + enum dtmf_modes_t {DTMF_MODE_UNAUTH, DTMF_MODE_ROOT, DTMF_MODE_STAR}; namespace sip { @@ -62,9 +67,25 @@ namespace sip { class _MumlibAudioMedia; + struct call { + unsigned index; + std::unique_ptr mixer; + std::unique_ptr media; + pj_caching_pool cachingPool; + std::function onStateChange; + std::function onIncomingPcmSamples; + std::function onMuteDeafChange; + std::function sendUserState; + std::function onConnect; + std::function onDisconnect; + std::function onCallerAuth; + std::function joinAuthChannel; + std::function joinDefaultChannel; + }; + class PjsuaCommunicator : boost::noncopyable { public: - PjsuaCommunicator(IncomingConnectionValidator &validator, int frameTimeLength); + PjsuaCommunicator(IncomingConnectionValidator &validator, int frameTimeLength, int maxCalls); void connect( std::string host, @@ -75,6 +96,7 @@ namespace sip { virtual ~PjsuaCommunicator(); void sendPcmSamples( + int callId, int sessionId, int sequenceNumber, int16_t *samples, @@ -97,29 +119,18 @@ namespace sip { dtmf_modes_t dtmf_mode = DTMF_MODE_ROOT; int pin_fails = 0; - std::function onIncomingPcmSamples; - - std::function onStateChange; - - std::function onMuteDeafChange; - - std::function onMuteChange; - pj_status_t mediaPortGetFrame(pjmedia_port *port, pjmedia_frame *frame); pj_status_t mediaPortPutFrame(pjmedia_port *port, pjmedia_frame *frame); + call calls[MY_MAX_CALLS]; + private: log4cpp::Category &logger; log4cpp::Category &pjsuaLogger; - std::unique_ptr mixer; - std::unique_ptr<_LogWriter> logWriter; std::unique_ptr<_Account> account; - std::unique_ptr<_MumlibAudioMedia> media; - - pj_caching_pool cachingPool; pj::Endpoint endpoint; diff --git a/README.md b/README.md index 9a3dd99..d1b11e8 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,91 @@ Remember to add URIs which you want to make calls from. Calls from other URIs wo ./mumsi config.ini ``` +## Configuring + +### Multi-Line Support + +If your SIP provider allows multiple simultaneous calls, mumsi can be configured to accept +calls and map them to separate Mumble users. The max\_calls is configure in *config.ini*: + +``` +[sip] +... +max_calls = 32 +... +``` + +Currently, the Mumble connections are established at server start. For usability, the following +options are recommended: + +* caller\_pin +* autodeaf +* channelAuthExpression + +The maximum number of calls is set in *main.hpp* and should not exceed the +*PJSUA_MAX_CALLS* in *pjsua.h*, which by default is 32. This can also be recompiled to +more, if desired. + +When mumsi logs into Mumble, it uses the user name from *config.ini* and appends +the character '-', followed by the connection number (counter). + +*LIMITATIONS:* The code is _alpha_ and needs testing/debugging, especialy in +the code that uses mumlib::Transport. Also, there is initial work on connecting +the Mumble user only when the SIP call is active, so the UI for other users is +better, but this code is still very buggy and therefore disabled. + +### Caller PIN + +When the caller\_pin is set, the incoming SIP connection is mute/deaf until the +caller enters the correct PIN, followed by the '#' symbol. On three failed +attempts, the SIP connection is hung up. On success, the Mumble user is moved +into the channel matching channelAuthExpression, if specified, and then mute/deaf +is turned off. As a courtesy to the other users, a brief announcement audio +file is played in the Mumble channel. + +The caller\_pin is configured in *config.ini* in the *app* section: + +``` +[app] +caller_pin = 12345 +``` + +In addition to the caller\_pin, a channelAuthExpression can be set. After +the caller authenticates with the PIN, the mumsi Mumble user will switch +to the Mumble channel that matches this expression. When the call is +completed, the mumsi Mumble user will return to the default channel that +matches channelNameExpression. + +This helps keep the unused SIP connections from cluttering your channel. + +### Autodeaf + +By default (i.e. autodeaf=0), other Mumble users can only see whether the mumsi +connection has an active caller if they are in the same channel. This is becaue +the 'talking mouth' icon is not visible to users in other channels. The mute/deaf +icons, on the other hand, can be seen by Mumble users when they are in different +channels, making it easier to spot when a new caller has connected. + +Setting `autodeaf=1' causes the mumsi Mumble user to be mute/deaf when there +is no active SIP call. + +### Audio Files + +When certain events occur, it is user-friendly to provide some sort of prompting +confirmation to the user. An example set of WAV files is provided, but they +can easily be customized or replaced with local versions, if needed. If the +files are not found, no sound is played. The following events are supported: + +- welcome: Played to caller when first connecting to mumsi +- prompt\_pin: Prompt the caller to enter the PIN +- entering\_channel: Caller entered PIN and is now entering the Mumble channel +- announce\_new\_caller: Played to the Mumble channel when adding a new caller +- invalid\_pin: Let the caller know they entered the wrong PIN +- goodbye: Hanging up on the caller +- mute\_on: Self-mute has been turned on (not implemented) +- mute\_off: Self-mute has been turned off (not implemented) +- menu: Tell caller the menu options (not implemented) + ## Start at boot *mumsi* provides no *init.d* scripts, but you can use great daemon mangaer, [Supervisor](http://supervisord.org/). @@ -65,7 +150,6 @@ stdout_capture_maxbytes=1MB redirect_stderr=true ``` - ## Issues #### Port and NAT @@ -82,6 +166,12 @@ pjsua_conf_add_port(mediaPool, (pjmedia_port *)port, &id) error: Invalid operati Some older versions of PJSIP are affected (confirmed for 2.3). In this case you have to update PJSIP to most recent version (2.4.5). +#### mumlib::TrasportException + +The multi-caller code is _alpha_ and needs testing/debugging, especialy in +the code that uses mumlib::Transport. Also, there is initial work on connecting +the Mumble user only when the SIP call is active, so the UI for other users is +better, but this code is still very buggy and therefore disabled. ## TODO: diff --git a/main.cpp b/main.cpp index 8396f49..d76b6f0 100644 --- a/main.cpp +++ b/main.cpp @@ -10,6 +10,8 @@ #include +#include "main.hpp" + /* * Code from http://stackoverflow.com/a/77336/5419223 */ @@ -26,13 +28,15 @@ static void sigsegv_handler(int sig) { int main(int argc, char *argv[]) { signal(SIGSEGV, sigsegv_handler); + int max_calls; log4cpp::OstreamAppender appender("console", &std::cout); log4cpp::PatternLayout layout; layout.setConversionPattern("%d [%p] %c: %m%n"); appender.setLayout(&layout); log4cpp::Category &logger = log4cpp::Category::getRoot(); - logger.setPriority(log4cpp::Priority::NOTICE); + logger.setPriority(log4cpp::Priority::DEBUG); + //logger.setPriority(log4cpp::Priority::NOTICE); logger.addAppender(appender); if (argc == 1) { @@ -48,40 +52,13 @@ int main(int argc, char *argv[]) { boost::asio::io_service ioService; - sip::PjsuaCommunicator pjsuaCommunicator(connectionValidator, conf.getInt("sip.frameLength")); + try { + max_calls = conf.getInt("sip.max_calls"); + } catch (...) { + max_calls = 1; + } - mumble::MumbleCommunicator mumbleCommunicator(ioService); - - mumble::MumbleChannelJoiner mumbleChannelJoiner(conf.getString("mumble.channelNameExpression")); - - using namespace std::placeholders; - pjsuaCommunicator.onIncomingPcmSamples = std::bind( - &mumble::MumbleCommunicator::sendPcmSamples, - &mumbleCommunicator, - _1, _2); - - pjsuaCommunicator.onStateChange = std::bind( - &mumble::MumbleCommunicator::sendTextMessage, - &mumbleCommunicator, _1); - - pjsuaCommunicator.onMuteDeafChange = std::bind( - &mumble::MumbleCommunicator::mutedeaf, - &mumbleCommunicator, _1); - - mumbleCommunicator.onIncomingPcmSamples = std::bind( - &sip::PjsuaCommunicator::sendPcmSamples, - &pjsuaCommunicator, - _1, _2, _3, _4); - - mumbleCommunicator.onIncomingChannelState = std::bind( - &mumble::MumbleChannelJoiner::checkChannel, - &mumbleChannelJoiner, - _1, _2); - - mumbleCommunicator.onServerSync = std::bind( - &mumble::MumbleChannelJoiner::maybeJoinChannel, - &mumbleChannelJoiner, - &mumbleCommunicator); + sip::PjsuaCommunicator pjsuaCommunicator(connectionValidator, conf.getInt("sip.frameLength"), max_calls); mumble::MumbleCommunicatorConfig mumbleConf; mumbleConf.host = conf.getString("mumble.host"); @@ -96,6 +73,18 @@ int main(int argc, char *argv[]) { mumbleConf.autodeaf = false; } + try { + mumbleConf.comment = conf.getString("app.comment"); + } catch (...) { + mumbleConf.comment = ""; + } + + try { + mumbleConf.authchan = conf.getString("mumble.channelAuthExpression"); + } catch (...) { + mumbleConf.authchan = ""; + } + /* default to */ try { pjsuaCommunicator.caller_pin = conf.getString("app.caller_pin"); @@ -148,7 +137,111 @@ int main(int argc, char *argv[]) { pjsuaCommunicator.file_menu = "menu.wav"; } - mumbleCommunicator.connect(mumbleConf); + /* If the channelUnauthExpression is set, use this as the default + * channel and use channelNameExpression for the authchan. Otherwise, + * use the channelNameExpression for the default. + */ + std::string defaultChan = conf.getString("mumble.channelNameExpression"); + std::string authChan = ""; + + if ( pjsuaCommunicator.caller_pin.size() > 0 ) { + try { + authChan = conf.getString("mumble.channelAuthExpression"); + } catch (...) { + // defaultChan = conf.getString("mumble.channelNameExpression"); + } + } + + mumble::MumbleChannelJoiner mumbleChannelJoiner(defaultChan); + mumble::MumbleChannelJoiner mumbleAuthChannelJoiner(authChan); + + for (int i = 0; icallId = i; + + using namespace std::placeholders; + // Passing audio input from SIP to Mumble + pjsuaCommunicator.calls[i].onIncomingPcmSamples = std::bind( + &mumble::MumbleCommunicator::sendPcmSamples, + mumcom, + _1, _2); + + // PJ sends text message to Mumble + pjsuaCommunicator.calls[i].onStateChange = std::bind( + &mumble::MumbleCommunicator::sendTextMessage, + mumcom, + _1); + + /* + // Send mute/deaf to Mumble + pjsuaCommunicator.calls[i].onMuteDeafChange = std::bind( + &mumble::MumbleCommunicator::mutedeaf, + mumcom, + _1); + */ + + // Send UserState to Mumble + pjsuaCommunicator.calls[i].sendUserState = std::bind( + &mumble::MumbleCommunicator::sendUserState, + mumcom, + _1, _2); + + // PJ triggers Mumble connect + pjsuaCommunicator.calls[i].onConnect = std::bind( + &mumble::MumbleCommunicator::onConnect, + mumcom); + + // PJ triggers Mumble disconnect + pjsuaCommunicator.calls[i].onDisconnect = std::bind( + &mumble::MumbleCommunicator::onDisconnect, + mumcom); + + // PJ notifies Mumble that Caller Auth is done + pjsuaCommunicator.calls[i].onCallerAuth = std::bind( + &mumble::MumbleCommunicator::onCallerAuth, + mumcom); + + /* + // PJ notifies Mumble that Caller Auth is done + pjsuaCommunicator.calls[i].onCallerUnauth = std::bind( + &mumble::MumbleCommunicator::onCallerUnauth, + mumcom); + */ + + // PJ notifies Mumble that Caller Auth is done + pjsuaCommunicator.calls[i].joinDefaultChannel = std::bind( + &mumble::MumbleChannelJoiner::findJoinChannel, + &mumbleChannelJoiner, + mumcom); + + // PJ notifies Mumble that Caller Auth is done + pjsuaCommunicator.calls[i].joinAuthChannel = std::bind( + &mumble::MumbleChannelJoiner::findJoinChannel, + &mumbleAuthChannelJoiner, + mumcom); + + // Passing audio from Mumble to SIP + mumcom->onIncomingPcmSamples = std::bind( + &sip::PjsuaCommunicator::sendPcmSamples, + &pjsuaCommunicator, + _1, _2, _3, _4, _5); + + // Handle Channel State messages from Mumble + mumcom->onIncomingChannelState = std::bind( + &mumble::MumbleChannelJoiner::checkChannel, + &mumbleChannelJoiner, + _1, _2); + + // Handle Server Sync message from Mumble + mumcom->onServerSync = std::bind( + &mumble::MumbleChannelJoiner::maybeJoinChannel, + &mumbleChannelJoiner, + mumcom); + + mumbleConf.user = conf.getString("mumble.user") + '-' + std::to_string(i); + mumcom->connect(mumbleConf); + } pjsuaCommunicator.connect( conf.getString("sip.host"), diff --git a/main.hpp b/main.hpp new file mode 100644 index 0000000..501350c --- /dev/null +++ b/main.hpp @@ -0,0 +1,6 @@ +#pragma once + +// IMPORTANT: The default PJSUA_MAX_CALLS in pjsua.h is 32. +// If you need more, you'll need to re-compile pjsua, too. +// +#define MY_MAX_CALLS 32 diff --git a/media/prompt-pin.msg b/media/prompt-pin.msg index 75a9622..b64d9b8 100644 --- a/media/prompt-pin.msg +++ b/media/prompt-pin.msg @@ -1 +1 @@ -Please enter pin +Please enter pin, followed by the hash symbol diff --git a/media/prompt-pin.wav b/media/prompt-pin.wav index d3a6ebd..fe5eecf 100644 Binary files a/media/prompt-pin.wav and b/media/prompt-pin.wav differ