initial multi-call support; improve channel joins

This commit is contained in:
Scott Hardin 2017-05-24 22:38:45 +02:00
parent 82015dc14c
commit 993c5f3988
11 changed files with 474 additions and 103 deletions

View File

@ -5,12 +5,20 @@ using namespace std;
mumble::MumbleChannelJoiner::MumbleChannelJoiner(std::string channelNameRegex) : channelNameRegex(boost::regex(channelNameRegex)), mumble::MumbleChannelJoiner::MumbleChannelJoiner(std::string channelNameRegex) : channelNameRegex(boost::regex(channelNameRegex)),
logger(log4cpp::Category::getInstance("MumbleChannelJoiner")){ logger(log4cpp::Category::getInstance("MumbleChannelJoiner")){
//std::vector<ChannelEntry> *channels = new std::vector<ChannelEntry>();
} }
std::vector<mumble::MumbleChannelJoiner::ChannelEntry> mumble::MumbleChannelJoiner::channels;
void mumble::MumbleChannelJoiner::checkChannel(std::string channel_name, int channel_id) { void mumble::MumbleChannelJoiner::checkChannel(std::string channel_name, int channel_id) {
boost::smatch s; boost::smatch s;
ChannelEntry ent;
logger.debug("Channel %s available (%d)", channel_name.c_str(), channel_id); 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)) { if(boost::regex_match(channel_name, s, channelNameRegex)) {
this->channel_id = channel_id; 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<ChannelEntry>::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);
}
}

View File

@ -3,21 +3,30 @@
#include <boost/noncopyable.hpp> #include <boost/noncopyable.hpp>
#include <log4cpp/Category.hh> #include <log4cpp/Category.hh>
#include <vector>
#include <string> #include <string>
#include <boost/regex.hpp> #include <boost/regex.hpp>
#include "MumbleCommunicator.hpp" #include "MumbleCommunicator.hpp"
namespace mumble { namespace mumble {
class MumbleChannelJoiner : boost::noncopyable { class MumbleChannelJoiner : boost::noncopyable {
struct ChannelEntry {
int id;
std::string name;
};
public: public:
MumbleChannelJoiner(std::string channelNameRegex); MumbleChannelJoiner(std::string channelNameRegex);
void checkChannel(std::string channel_name, int channel_id); void checkChannel(std::string channel_name, int channel_id);
void maybeJoinChannel(mumble::MumbleCommunicator *mc); void maybeJoinChannel(mumble::MumbleCommunicator *mc);
void findJoinChannel(mumble::MumbleCommunicator *mc);
private: private:
log4cpp::Category &logger; log4cpp::Category &logger;
boost::regex channelNameRegex; boost::regex channelNameRegex;
int channel_id; int channel_id;
static std::vector<ChannelEntry> channels;
}; };
} }

View File

@ -9,13 +9,14 @@ namespace mumble {
std::shared_ptr<mumlib::Mumlib> mum; std::shared_ptr<mumlib::Mumlib> mum;
MumbleCommunicator *communicator; MumbleCommunicator *communicator;
// called by Mumlib when receiving audio from mumble server
virtual void audio( virtual void audio(
int target, int target,
int sessionId, int sessionId,
int sequenceNumber, int sequenceNumber,
int16_t *pcm_data, int16_t *pcm_data,
uint32_t pcm_data_size) override { 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( virtual void channelState(
@ -39,6 +40,26 @@ namespace mumble {
communicator->onServerSync(); 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->communicator = this;
callback->mum = mum; callback->mum = mum;
// IMPORTANT: comment these out when experimenting with onConnect
if ( ! MUM_DELAYED_CONNECT ) {
mum->connect(config.host, config.port, config.user, config.password); mum->connect(config.host, config.port, config.user, config.password);
mum->self_deaf(1); 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) { void mumble::MumbleCommunicator::sendPcmSamples(int16_t *samples, unsigned int length) {
@ -76,21 +126,45 @@ void mumble::MumbleCommunicator::sendTextMessage(std::string message) {
mum->sendTextMessage(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) { void mumble::MumbleCommunicator::joinChannel(int channel_id) {
mum->joinChannel(channel_id); mum->joinChannel(channel_id);
if ( mumbleConf.autodeaf ) { if ( mumbleConf.autodeaf ) {
//mum->self_mute(1); mum->sendUserState(mumlib::UserState::SELF_DEAF, true);
mum->self_deaf(1);
} }
} }
void mumble::MumbleCommunicator::mutedeaf(int status) {
if ( mumbleConf.autodeaf ) { void mumble::MumbleCommunicator::sendUserState(mumlib::UserState field, bool val) {
if ( status ) { mum->sendUserState(field, val);
mum->self_deaf(status);
} else {
mum->self_mute(status);
}
}
} }

View File

@ -8,6 +8,9 @@
#include <string> #include <string>
#include <stdexcept> #include <stdexcept>
// 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 { namespace mumble {
@ -26,6 +29,20 @@ namespace mumble {
int opusEncoderBitrate; int opusEncoderBitrate;
int port = 0; int port = 0;
bool autodeaf; 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 { class MumbleCommunicator : boost::noncopyable {
@ -34,6 +51,10 @@ namespace mumble {
boost::asio::io_service &ioService); boost::asio::io_service &ioService);
void connect(MumbleCommunicatorConfig &config); void connect(MumbleCommunicatorConfig &config);
void onConnect();
void onDisconnect();
void onCallerAuth();
//void onCallerUnauth();
virtual ~MumbleCommunicator(); virtual ~MumbleCommunicator();
@ -41,9 +62,9 @@ namespace mumble {
/** /**
* This callback is called when communicator has received samples. * 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<void(int, int, int16_t *, int)> onIncomingPcmSamples; std::function<void(int, int, int, int16_t *, int)> onIncomingPcmSamples;
/** /**
* This callback is called when a channel state message (e.g. Channel * This callback is called when a channel state message (e.g. Channel
@ -53,11 +74,17 @@ namespace mumble {
std::function<void()> onServerSync; std::function<void()> onServerSync;
std::function<void()> onUserState;
void sendTextMessage(std::string message); void sendTextMessage(std::string message);
void joinChannel(int channel_id); void joinChannel(int channel_id);
void mutedeaf(int status); void sendUserState(mumlib::UserState field, bool val);
MumbleUserState userState;
int callId;
private: private:
boost::asio::io_service &ioService; boost::asio::io_service &ioService;

View File

@ -6,6 +6,8 @@
#include <boost/algorithm/string.hpp> #include <boost/algorithm/string.hpp>
#include <boost/format.hpp> #include <boost/format.hpp>
#include "main.hpp"
using namespace std; using namespace std;
namespace sip { namespace sip {
@ -38,9 +40,9 @@ namespace sip {
class _MumlibAudioMedia : public pj::AudioMedia { class _MumlibAudioMedia : public pj::AudioMedia {
public: public:
_MumlibAudioMedia(sip::PjsuaCommunicator &comm, int frameTimeLength) _MumlibAudioMedia(int call_id, sip::PjsuaCommunicator &comm, int frameTimeLength)
: communicator(comm) { : communicator(comm) {
createMediaPort(frameTimeLength); createMediaPort(call_id, frameTimeLength);
registerMediaPort(&mediaPort); registerMediaPort(&mediaPort);
} }
@ -62,7 +64,7 @@ namespace sip {
return communicator->mediaPortPutFrame(port, frame); return communicator->mediaPortPutFrame(port, frame);
} }
void createMediaPort(int frameTimeLength) { void createMediaPort(int call_id, int frameTimeLength) {
auto name = pj_str((char *) "MumsiMediaPort"); auto name = pj_str((char *) "MumsiMediaPort");
@ -88,6 +90,8 @@ namespace sip {
} }
mediaPort.port_data.pdata = &communicator; mediaPort.port_data.pdata = &communicator;
// track call id in port_data
mediaPort.port_data.ldata = (long) call_id;
mediaPort.get_frame = &callback_getFrame; mediaPort.get_frame = &callback_getFrame;
mediaPort.put_frame = &callback_putFrame; mediaPort.put_frame = &callback_putFrame;
@ -145,14 +149,19 @@ namespace sip {
if (ci.state == PJSIP_INV_STATE_CONFIRMED) { if (ci.state == PJSIP_INV_STATE_CONFIRMED) {
auto msgText = "Incoming call from " + address + "."; 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.logger.notice(msgText);
communicator.onStateChange(msgText); communicator.calls[ci.id].onStateChange(msgText);
pj_thread_sleep(500); // sleep a moment to allow connection to stabilize pj_thread_sleep(500); // sleep a moment to allow connection to stabilize
this->playAudioFile(communicator.file_welcome); this->playAudioFile(communicator.file_welcome);
communicator.got_dtmf = ""; 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 * if no pin is set, go ahead and turn off mute/deaf
* otherwise, wait for pin to be entered * otherwise, wait for pin to be entered
@ -160,11 +169,18 @@ namespace sip {
if ( communicator.caller_pin.length() == 0 ) { if ( communicator.caller_pin.length() == 0 ) {
// No PIN set... enter DTMF root menu and turn off mute/deaf // No PIN set... enter DTMF root menu and turn off mute/deaf
communicator.dtmf_mode = DTMF_MODE_ROOT; 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 { } else {
// PIN set... enter DTMF unauth menu and play PIN prompt message // PIN set... enter DTMF unauth menu and play PIN prompt message
communicator.dtmf_mode = DTMF_MODE_UNAUTH; 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 pj_thread_sleep(500); // pause briefly after announcement
communicator.logger.notice("MYDEBUG: call play...()");
this->playAudioFile(communicator.file_prompt_pin); this->playAudioFile(communicator.file_prompt_pin);
} }
@ -174,16 +190,23 @@ namespace sip {
if (not acc.available) { if (not acc.available) {
auto msgText = "Call from " + address + " finished."; auto msgText = "Call from " + address + " finished.";
communicator.mixer->clear(); communicator.calls[ci.id].mixer->clear();
communicator.logger.notice(msgText); communicator.logger.notice(msgText);
communicator.onStateChange(msgText); communicator.calls[ci.id].onStateChange(msgText);
communicator.onMuteDeafChange(1); 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; acc.available = true;
} }
delete this; 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) { if (ci.media[0].status == PJSUA_CALL_MEDIA_ACTIVE) {
auto *aud_med = static_cast<pj::AudioMedia *>(getMedia(0)); auto *aud_med = static_cast<pj::AudioMedia *>(getMedia(0));
communicator.media->startTransmit(*aud_med); communicator.calls[ci.id].media->startTransmit(*aud_med);
aud_med->startTransmit(*communicator.media); aud_med->startTransmit(*communicator.calls[ci.id].media);
} else if (ci.media[0].status == PJSUA_CALL_MEDIA_NONE) { } else if (ci.media[0].status == PJSUA_CALL_MEDIA_NONE) {
dynamic_cast<_Account &>(account).available = true; dynamic_cast<_Account &>(account).available = true;
} }
@ -244,14 +267,8 @@ namespace sip {
pinfo = player.getInfo(); pinfo = player.getInfo();
sleeptime = pinfo.sizeBytes / (pinfo.payloadBitsPerSample * 3); 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 if ( in_chan ) { // choose the target sound output
player.startTransmit(*communicator.media); player.startTransmit(*communicator.calls[ci.id].media);
} else { } else {
player.startTransmit(*aud_med); player.startTransmit(*aud_med);
} }
@ -259,7 +276,7 @@ namespace sip {
pj_thread_sleep(sleeptime); pj_thread_sleep(sleeptime);
if ( in_chan ) { // choose the target sound output if ( in_chan ) { // choose the target sound output
player.stopTransmit(*communicator.media); player.stopTransmit(*communicator.calls[ci.id].media);
} else { } else {
player.stopTransmit(*aud_med); player.stopTransmit(*aud_med);
} }
@ -277,6 +294,8 @@ namespace sip {
// prm.digit.c_str(), getId()); // prm.digit.c_str(), getId());
pj::CallOpParam param; pj::CallOpParam param;
auto ci = getInfo();
/* /*
* DTMF CALLER MENU * DTMF CALLER MENU
*/ */
@ -295,8 +314,10 @@ namespace sip {
if ( communicator.got_dtmf == communicator.caller_pin ) { if ( communicator.got_dtmf == communicator.caller_pin ) {
communicator.logger.notice("Caller entered correct PIN"); communicator.logger.notice("Caller entered correct PIN");
communicator.dtmf_mode = DTMF_MODE_ROOT; communicator.dtmf_mode = DTMF_MODE_ROOT;
communicator.calls[ci.id].joinAuthChannel();
this->playAudioFile(communicator.file_entering_channel); 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); this->playAudioFile(communicator.file_announce_new_caller, true);
} else { } else {
communicator.logger.notice("Caller entered wrong PIN"); communicator.logger.notice("Caller entered wrong PIN");
@ -357,18 +378,20 @@ namespace sip {
* User already entered '*'; time to perform action * User already entered '*'; time to perform action
*/ */
switch ( prm.digit[0] ) { switch ( prm.digit[0] ) {
/*
case '5': case '5':
// Mute line // Mute line
communicator.onMuteChange(1); communicator.calls[ci.id].sendUserState(mumlib::UserState::SELF_MUTE, true);
this->playAudioFile(communicator.file_mute_on); this->playAudioFile(communicator.file_mute_on);
break; break;
case '6': case '6':
// Un-mute line // Un-mute line
this->playAudioFile(communicator.file_mute_off); 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; break;
*/
default: default:
communicator.logger.notice("Unsupported DTMF digit '%s' in state STAR", prm.digit.c_str()); 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; param.statusCode = PJSIP_SC_OK;
available = false; available = false;
} else { } 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); 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")), : logger(log4cpp::Category::getInstance("SipCommunicator")),
pjsuaLogger(log4cpp::Category::getInstance("Pjsua")), pjsuaLogger(log4cpp::Category::getInstance("Pjsua")),
uriValidator(validator) { uriValidator(validator) {
logWriter.reset(new sip::_LogWriter(pjsuaLogger)); logWriter.reset(new sip::_LogWriter(pjsuaLogger));
endpoint.libCreate(); endpoint.libCreate();
pj::EpConfig endpointConfig; pj::EpConfig endpointConfig;
endpointConfig.uaConfig.userAgent = "Mumsi Mumble-SIP gateway"; endpointConfig.uaConfig.userAgent = "Mumsi Mumble-SIP gateway";
endpointConfig.uaConfig.maxCalls = 1; endpointConfig.uaConfig.maxCalls = maxCalls;
endpointConfig.logConfig.writer = logWriter.get(); endpointConfig.logConfig.writer = logWriter.get();
endpointConfig.logConfig.level = 5; endpointConfig.logConfig.level = 5;
@ -437,11 +467,12 @@ sip::PjsuaCommunicator::PjsuaCommunicator(IncomingConnectionValidator &validator
endpoint.libInit(endpointConfig); endpoint.libInit(endpointConfig);
pj_caching_pool_init(&cachingPool, &pj_pool_factory_default_policy, 0); for(int i=0; i<maxCalls; ++i) {
calls[i].index = i;
mixer.reset(new mixer::AudioFramesMixer(cachingPool.factory)); pj_caching_pool_init(&(calls[i].cachingPool), &pj_pool_factory_default_policy, 0);
calls[i].mixer.reset(new mixer::AudioFramesMixer(calls[i].cachingPool.factory));
media.reset(new _MumlibAudioMedia(*this, frameTimeLength)); calls[i].media.reset(new _MumlibAudioMedia(i, *this, frameTimeLength));
}
logger.info("Created Pjsua communicator with frame length %d ms.", frameTimeLength); logger.info("Created Pjsua communicator with frame length %d ms.", frameTimeLength);
} }
@ -472,8 +503,8 @@ sip::PjsuaCommunicator::~PjsuaCommunicator() {
endpoint.libDestroy(); endpoint.libDestroy();
} }
void sip::PjsuaCommunicator::sendPcmSamples(int sessionId, int sequenceNumber, int16_t *samples, unsigned int length) { void sip::PjsuaCommunicator::sendPcmSamples(int callId, int sessionId, int sequenceNumber, int16_t *samples, unsigned int length) {
mixer->addFrameToBuffer(sessionId, sequenceNumber, samples, length); calls[callId].mixer->addFrameToBuffer(sessionId, sequenceNumber, samples, length);
} }
pj_status_t sip::PjsuaCommunicator::mediaPortGetFrame(pjmedia_port *port, pjmedia_frame *frame) { 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<pj_int16_t *>(frame->buf); pj_int16_t *samples = static_cast<pj_int16_t *>(frame->buf);
pj_size_t count = frame->size / 2 / PJMEDIA_PIA_CCNT(&(port->info)); 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) { if (readSamples < count) {
pjsuaLogger.debug("Requested %d samples, available %d, filling remaining with zeros.", 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); pj_size_t count = frame->size / 2 / PJMEDIA_PIA_CCNT(&port->info);
frame->type = PJMEDIA_FRAME_TYPE_AUDIO; frame->type = PJMEDIA_FRAME_TYPE_AUDIO;
int call_id = (int) port->port_data.ldata;
if (count > 0) { if (count > 0) {
pjsuaLogger.debug("Calling onIncomingPcmSamples with %d samples.", count); pjsuaLogger.debug("Calling onIncomingPcmSamples with %d samples (call_id=%d).", count, call_id);
onIncomingPcmSamples(samples, count); this->calls[call_id].onIncomingPcmSamples(samples, count);
} }
return PJ_SUCCESS; return PJ_SUCCESS;

View File

@ -18,6 +18,11 @@
#include <climits> #include <climits>
#include <bits/unique_ptr.h> #include <bits/unique_ptr.h>
// for userState enum
#include <mumlib.hpp>
#include "main.hpp"
enum dtmf_modes_t {DTMF_MODE_UNAUTH, DTMF_MODE_ROOT, DTMF_MODE_STAR}; enum dtmf_modes_t {DTMF_MODE_UNAUTH, DTMF_MODE_ROOT, DTMF_MODE_STAR};
namespace sip { namespace sip {
@ -62,9 +67,25 @@ namespace sip {
class _MumlibAudioMedia; class _MumlibAudioMedia;
struct call {
unsigned index;
std::unique_ptr<mixer::AudioFramesMixer> mixer;
std::unique_ptr<sip::_MumlibAudioMedia> media;
pj_caching_pool cachingPool;
std::function<void(std::string)> onStateChange;
std::function<void(int16_t *, int)> onIncomingPcmSamples;
std::function<void(int)> onMuteDeafChange;
std::function<void(mumlib::UserState field, bool val)> sendUserState;
std::function<void()> onConnect;
std::function<void()> onDisconnect;
std::function<void()> onCallerAuth;
std::function<void()> joinAuthChannel;
std::function<void()> joinDefaultChannel;
};
class PjsuaCommunicator : boost::noncopyable { class PjsuaCommunicator : boost::noncopyable {
public: public:
PjsuaCommunicator(IncomingConnectionValidator &validator, int frameTimeLength); PjsuaCommunicator(IncomingConnectionValidator &validator, int frameTimeLength, int maxCalls);
void connect( void connect(
std::string host, std::string host,
@ -75,6 +96,7 @@ namespace sip {
virtual ~PjsuaCommunicator(); virtual ~PjsuaCommunicator();
void sendPcmSamples( void sendPcmSamples(
int callId,
int sessionId, int sessionId,
int sequenceNumber, int sequenceNumber,
int16_t *samples, int16_t *samples,
@ -97,29 +119,18 @@ namespace sip {
dtmf_modes_t dtmf_mode = DTMF_MODE_ROOT; dtmf_modes_t dtmf_mode = DTMF_MODE_ROOT;
int pin_fails = 0; int pin_fails = 0;
std::function<void(int16_t *, int)> onIncomingPcmSamples;
std::function<void(std::string)> onStateChange;
std::function<void(int)> onMuteDeafChange;
std::function<void(int)> onMuteChange;
pj_status_t mediaPortGetFrame(pjmedia_port *port, pjmedia_frame *frame); pj_status_t mediaPortGetFrame(pjmedia_port *port, pjmedia_frame *frame);
pj_status_t mediaPortPutFrame(pjmedia_port *port, pjmedia_frame *frame); pj_status_t mediaPortPutFrame(pjmedia_port *port, pjmedia_frame *frame);
call calls[MY_MAX_CALLS];
private: private:
log4cpp::Category &logger; log4cpp::Category &logger;
log4cpp::Category &pjsuaLogger; log4cpp::Category &pjsuaLogger;
std::unique_ptr<mixer::AudioFramesMixer> mixer;
std::unique_ptr<_LogWriter> logWriter; std::unique_ptr<_LogWriter> logWriter;
std::unique_ptr<_Account> account; std::unique_ptr<_Account> account;
std::unique_ptr<_MumlibAudioMedia> media;
pj_caching_pool cachingPool;
pj::Endpoint endpoint; pj::Endpoint endpoint;

View File

@ -47,6 +47,91 @@ Remember to add URIs which you want to make calls from. Calls from other URIs wo
./mumsi config.ini ./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 ## Start at boot
*mumsi* provides no *init.d* scripts, but you can use great daemon mangaer, [Supervisor](http://supervisord.org/). *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 redirect_stderr=true
``` ```
## Issues ## Issues
#### Port and NAT #### 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). 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: ## TODO:

163
main.cpp
View File

@ -10,6 +10,8 @@
#include <execinfo.h> #include <execinfo.h>
#include "main.hpp"
/* /*
* Code from http://stackoverflow.com/a/77336/5419223 * Code from http://stackoverflow.com/a/77336/5419223
*/ */
@ -26,13 +28,15 @@ static void sigsegv_handler(int sig) {
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
signal(SIGSEGV, sigsegv_handler); signal(SIGSEGV, sigsegv_handler);
int max_calls;
log4cpp::OstreamAppender appender("console", &std::cout); log4cpp::OstreamAppender appender("console", &std::cout);
log4cpp::PatternLayout layout; log4cpp::PatternLayout layout;
layout.setConversionPattern("%d [%p] %c: %m%n"); layout.setConversionPattern("%d [%p] %c: %m%n");
appender.setLayout(&layout); appender.setLayout(&layout);
log4cpp::Category &logger = log4cpp::Category::getRoot(); log4cpp::Category &logger = log4cpp::Category::getRoot();
logger.setPriority(log4cpp::Priority::NOTICE); logger.setPriority(log4cpp::Priority::DEBUG);
//logger.setPriority(log4cpp::Priority::NOTICE);
logger.addAppender(appender); logger.addAppender(appender);
if (argc == 1) { if (argc == 1) {
@ -48,40 +52,13 @@ int main(int argc, char *argv[]) {
boost::asio::io_service ioService; 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); sip::PjsuaCommunicator pjsuaCommunicator(connectionValidator, conf.getInt("sip.frameLength"), max_calls);
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);
mumble::MumbleCommunicatorConfig mumbleConf; mumble::MumbleCommunicatorConfig mumbleConf;
mumbleConf.host = conf.getString("mumble.host"); mumbleConf.host = conf.getString("mumble.host");
@ -96,6 +73,18 @@ int main(int argc, char *argv[]) {
mumbleConf.autodeaf = false; 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 <no pin> */ /* default to <no pin> */
try { try {
pjsuaCommunicator.caller_pin = conf.getString("app.caller_pin"); pjsuaCommunicator.caller_pin = conf.getString("app.caller_pin");
@ -148,7 +137,111 @@ int main(int argc, char *argv[]) {
pjsuaCommunicator.file_menu = "menu.wav"; 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; i<max_calls; i++) {
auto *mumcom = new mumble::MumbleCommunicator(ioService);
mumcom->callId = 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( pjsuaCommunicator.connect(
conf.getString("sip.host"), conf.getString("sip.host"),

6
main.hpp Normal file
View File

@ -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

View File

@ -1 +1 @@
Please enter pin Please enter pin, followed by the hash symbol

Binary file not shown.