initial multi-call support; improve channel joins
This commit is contained in:
parent
82015dc14c
commit
993c5f3988
@ -5,12 +5,20 @@ using namespace std;
|
||||
|
||||
mumble::MumbleChannelJoiner::MumbleChannelJoiner(std::string channelNameRegex) : channelNameRegex(boost::regex(channelNameRegex)),
|
||||
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) {
|
||||
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<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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,21 +3,30 @@
|
||||
#include <boost/noncopyable.hpp>
|
||||
#include <log4cpp/Category.hh>
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <boost/regex.hpp>
|
||||
#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<ChannelEntry> channels;
|
||||
};
|
||||
}
|
||||
|
@ -9,13 +9,14 @@ namespace mumble {
|
||||
std::shared_ptr<mumlib::Mumlib> 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);
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,9 @@
|
||||
#include <string>
|
||||
#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 {
|
||||
|
||||
@ -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<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
|
||||
@ -53,11 +74,17 @@ namespace mumble {
|
||||
|
||||
std::function<void()> onServerSync;
|
||||
|
||||
std::function<void()> 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;
|
||||
|
@ -6,6 +6,8 @@
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/format.hpp>
|
||||
|
||||
#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<pj::AudioMedia *>(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; i<maxCalls; ++i) {
|
||||
calls[i].index = i;
|
||||
pj_caching_pool_init(&(calls[i].cachingPool), &pj_pool_factory_default_policy, 0);
|
||||
calls[i].mixer.reset(new mixer::AudioFramesMixer(calls[i].cachingPool.factory));
|
||||
calls[i].media.reset(new _MumlibAudioMedia(i, *this, frameTimeLength));
|
||||
}
|
||||
|
||||
logger.info("Created Pjsua communicator with frame length %d ms.", frameTimeLength);
|
||||
}
|
||||
@ -472,8 +503,8 @@ sip::PjsuaCommunicator::~PjsuaCommunicator() {
|
||||
endpoint.libDestroy();
|
||||
}
|
||||
|
||||
void sip::PjsuaCommunicator::sendPcmSamples(int sessionId, int sequenceNumber, int16_t *samples, unsigned int length) {
|
||||
mixer->addFrameToBuffer(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<pj_int16_t *>(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;
|
||||
|
@ -18,6 +18,11 @@
|
||||
#include <climits>
|
||||
#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};
|
||||
|
||||
namespace sip {
|
||||
@ -62,9 +67,25 @@ namespace sip {
|
||||
|
||||
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 {
|
||||
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<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 mediaPortPutFrame(pjmedia_port *port, pjmedia_frame *frame);
|
||||
|
||||
call calls[MY_MAX_CALLS];
|
||||
|
||||
private:
|
||||
log4cpp::Category &logger;
|
||||
log4cpp::Category &pjsuaLogger;
|
||||
|
||||
std::unique_ptr<mixer::AudioFramesMixer> mixer;
|
||||
|
||||
std::unique_ptr<_LogWriter> logWriter;
|
||||
std::unique_ptr<_Account> account;
|
||||
std::unique_ptr<_MumlibAudioMedia> media;
|
||||
|
||||
pj_caching_pool cachingPool;
|
||||
|
||||
pj::Endpoint endpoint;
|
||||
|
||||
|
92
README.md
92
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:
|
||||
|
||||
|
163
main.cpp
163
main.cpp
@ -10,6 +10,8 @@
|
||||
|
||||
#include <execinfo.h>
|
||||
|
||||
#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 <no pin> */
|
||||
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; 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(
|
||||
conf.getString("sip.host"),
|
||||
|
6
main.hpp
Normal file
6
main.hpp
Normal 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
|
@ -1 +1 @@
|
||||
Please enter pin
|
||||
Please enter pin, followed by the hash symbol
|
||||
|
Binary file not shown.
Loading…
Reference in New Issue
Block a user