Merge remote-tracking branch 'mrscotty/develop'
This commit is contained in:
commit
6fb224e23f
@ -3,6 +3,7 @@
|
|||||||
#include <boost/property_tree/ptree.hpp>
|
#include <boost/property_tree/ptree.hpp>
|
||||||
#include <boost/property_tree/ini_parser.hpp>
|
#include <boost/property_tree/ini_parser.hpp>
|
||||||
#include <boost/format.hpp>
|
#include <boost/format.hpp>
|
||||||
|
#include <boost/foreach.hpp>
|
||||||
|
|
||||||
using namespace config;
|
using namespace config;
|
||||||
|
|
||||||
@ -50,4 +51,15 @@ std::string config::Configuration::getString(const std::string &property) {
|
|||||||
return get<std::string>(impl->ptree, property);
|
return get<std::string>(impl->ptree, property);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: return set
|
||||||
|
std::unordered_map<std::string, std::string> config::Configuration::getChildren(const std::string &property) {
|
||||||
|
std::unordered_map<std::string, std::string> pins;
|
||||||
|
BOOST_FOREACH(boost::property_tree::ptree::value_type &v,
|
||||||
|
impl->ptree.get_child(property)) {
|
||||||
|
//pins[v.first.data()] = get<std::string>(impl->ptree, property + "." + v.second.data());
|
||||||
|
pins[v.first.data()] = v.second.data();
|
||||||
|
}
|
||||||
|
return pins;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
namespace config {
|
namespace config {
|
||||||
|
|
||||||
@ -31,6 +32,8 @@ namespace config {
|
|||||||
|
|
||||||
std::string getString(const std::string &property);
|
std::string getString(const std::string &property);
|
||||||
|
|
||||||
|
std::unordered_map<std::string, std::string> getChildren(const std::string &property);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ConfigurationImpl *impl;
|
ConfigurationImpl *impl;
|
||||||
};
|
};
|
||||||
|
@ -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,28 @@ 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void mumble::MumbleChannelJoiner::joinOtherChannel(mumble::MumbleCommunicator *mc, std::string channelNameRegex) {
|
||||||
|
this->channelNameRegex = boost::regex(channelNameRegex);
|
||||||
|
findJoinChannel(mc);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -3,21 +3,31 @@
|
|||||||
#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);
|
||||||
|
void joinOtherChannel(mumble::MumbleCommunicator *mc, std::string channelNameRegex);
|
||||||
|
|
||||||
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;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
|
};
|
||||||
|
*/
|
||||||
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,14 +72,48 @@ void mumble::MumbleCommunicator::connect(MumbleCommunicatorConfig &config) {
|
|||||||
|
|
||||||
callback.reset(new MumlibCallback());
|
callback.reset(new MumlibCallback());
|
||||||
|
|
||||||
|
mumbleConf = config;
|
||||||
|
|
||||||
mumConfig = mumlib::MumlibConfiguration();
|
mumConfig = mumlib::MumlibConfiguration();
|
||||||
mumConfig.opusEncoderBitrate = config.opusEncoderBitrate;
|
mumConfig.opusEncoderBitrate = config.opusEncoderBitrate;
|
||||||
|
mumConfig.cert_file = config.cert_file;
|
||||||
|
mumConfig.privkey_file = config.privkey_file;
|
||||||
|
|
||||||
mum.reset(new mumlib::Mumlib(*callback, ioService, mumConfig));
|
mum.reset(new mumlib::Mumlib(*callback, ioService, mumConfig));
|
||||||
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);
|
||||||
|
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) {
|
||||||
@ -73,6 +128,49 @@ 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 ) {
|
||||||
|
mum->sendUserState(mumlib::UserState::SELF_DEAF, true);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void mumble::MumbleCommunicator::sendUserState(mumlib::UserState field, bool val) {
|
||||||
|
mum->sendUserState(field, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
void mumble::MumbleCommunicator::sendUserState(mumlib::UserState field, std::string val) {
|
||||||
|
mum->sendUserState(field, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -8,8 +8,13 @@
|
|||||||
#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 {
|
||||||
|
|
||||||
|
|
||||||
class Exception : public std::runtime_error {
|
class Exception : public std::runtime_error {
|
||||||
public:
|
public:
|
||||||
Exception(const char *message) : std::runtime_error(message) { }
|
Exception(const char *message) : std::runtime_error(message) { }
|
||||||
@ -21,8 +26,25 @@ namespace mumble {
|
|||||||
std::string user;
|
std::string user;
|
||||||
std::string password;
|
std::string password;
|
||||||
std::string host;
|
std::string host;
|
||||||
|
std::string cert_file;
|
||||||
|
std::string privkey_file;
|
||||||
int opusEncoderBitrate;
|
int opusEncoderBitrate;
|
||||||
int port = 0;
|
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 {
|
class MumbleCommunicator : boost::noncopyable {
|
||||||
@ -31,6 +53,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();
|
||||||
|
|
||||||
@ -38,9 +64,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
|
||||||
@ -50,15 +76,27 @@ 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 sendUserState(mumlib::UserState field, bool val);
|
||||||
|
|
||||||
|
void sendUserState(mumlib::UserState field, std::string val);
|
||||||
|
|
||||||
|
MumbleUserState userState;
|
||||||
|
|
||||||
|
int callId;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
boost::asio::io_service &ioService;
|
boost::asio::io_service &ioService;
|
||||||
|
|
||||||
log4cpp::Category &logger;
|
log4cpp::Category &logger;
|
||||||
|
|
||||||
|
MumbleCommunicatorConfig mumbleConf;
|
||||||
|
|
||||||
mumlib::MumlibConfiguration mumConfig;
|
mumlib::MumlibConfiguration mumConfig;
|
||||||
|
|
||||||
std::shared_ptr<mumlib::Mumlib> mum;
|
std::shared_ptr<mumlib::Mumlib> mum;
|
||||||
@ -66,5 +104,6 @@ namespace mumble {
|
|||||||
std::unique_ptr<MumlibCallback> callback;
|
std::unique_ptr<MumlibCallback> callback;
|
||||||
|
|
||||||
friend class MumlibCallback;
|
friend class MumlibCallback;
|
||||||
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
@ -107,6 +111,9 @@ namespace sip {
|
|||||||
|
|
||||||
virtual void onDtmfDigit(pj::OnDtmfDigitParam &prm) override;
|
virtual void onDtmfDigit(pj::OnDtmfDigitParam &prm) override;
|
||||||
|
|
||||||
|
virtual void playAudioFile(std::string file);
|
||||||
|
virtual void playAudioFile(std::string file, bool in_chan);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
sip::PjsuaCommunicator &communicator;
|
sip::PjsuaCommunicator &communicator;
|
||||||
pj::Account &account;
|
pj::Account &account;
|
||||||
@ -114,8 +121,8 @@ namespace sip {
|
|||||||
|
|
||||||
class _Account : public pj::Account {
|
class _Account : public pj::Account {
|
||||||
public:
|
public:
|
||||||
_Account(sip::PjsuaCommunicator &comm)
|
_Account(sip::PjsuaCommunicator &comm, int max_calls)
|
||||||
: communicator(comm) { }
|
: communicator(comm) { this->max_calls = max_calls; }
|
||||||
|
|
||||||
virtual void onRegState(pj::OnRegStateParam &prm) override;
|
virtual void onRegState(pj::OnRegStateParam &prm) override;
|
||||||
|
|
||||||
@ -124,7 +131,8 @@ namespace sip {
|
|||||||
private:
|
private:
|
||||||
sip::PjsuaCommunicator &communicator;
|
sip::PjsuaCommunicator &communicator;
|
||||||
|
|
||||||
bool available = true;
|
int active_calls = 0;
|
||||||
|
int max_calls;
|
||||||
|
|
||||||
friend class _Call;
|
friend class _Call;
|
||||||
};
|
};
|
||||||
@ -142,23 +150,67 @@ 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].sendUserStateStr(mumlib::UserState::COMMENT, 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 = "";
|
||||||
|
|
||||||
|
/*
|
||||||
|
* if no pin is set, go ahead and turn off mute/deaf
|
||||||
|
* otherwise, wait for pin to be entered
|
||||||
|
*/
|
||||||
|
if ( communicator.pins.size() == 0 ) {
|
||||||
|
// No PIN set... enter DTMF root menu and turn off mute/deaf
|
||||||
|
communicator.dtmf_mode = DTMF_MODE_ROOT;
|
||||||
|
// 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.calls[ci.id].joinDefaultChannel();
|
||||||
|
pj_thread_sleep(500); // pause briefly after announcement
|
||||||
|
|
||||||
|
this->playAudioFile(communicator.file_prompt_pin);
|
||||||
|
}
|
||||||
|
|
||||||
} else if (ci.state == PJSIP_INV_STATE_DISCONNECTED) {
|
} else if (ci.state == PJSIP_INV_STATE_DISCONNECTED) {
|
||||||
auto &acc = dynamic_cast<_Account &>(account);
|
auto &acc = dynamic_cast<_Account &>(account);
|
||||||
|
|
||||||
if (not acc.available) {
|
/*
|
||||||
|
* Not sure why we check acc.available, but with multi-call
|
||||||
|
* functionality, this check doesn't work.
|
||||||
|
*/
|
||||||
|
//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].sendUserStateStr(mumlib::UserState::COMMENT, msgText);
|
||||||
|
communicator.calls[ci.id].onStateChange(msgText);
|
||||||
|
communicator.calls[ci.id].sendUserState(mumlib::UserState::SELF_DEAF, true);
|
||||||
|
communicator.calls[ci.id].joinDefaultChannel();
|
||||||
|
|
||||||
acc.available = true;
|
communicator.calls[ci.id].onDisconnect();
|
||||||
}
|
|
||||||
|
//acc.available = true;
|
||||||
|
acc.active_calls--;
|
||||||
|
//}
|
||||||
|
|
||||||
delete this;
|
delete this;
|
||||||
|
} else {
|
||||||
|
communicator.logger.notice("MYDEBUG: unexpected state in onCallState() call:%d state:%d",
|
||||||
|
ci.id, ci.state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -172,15 +224,207 @@ 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).active_calls++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _Call::playAudioFile(std::string file) {
|
||||||
|
this->playAudioFile(file, false); // default is NOT to echo to mumble
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TODO:
|
||||||
|
* - local deafen before playing and undeafen after?
|
||||||
|
*/
|
||||||
|
void _Call::playAudioFile(std::string file, bool in_chan) {
|
||||||
|
communicator.logger.info("Entered playAudioFile(%s)", file.c_str());
|
||||||
|
pj::AudioMediaPlayer player;
|
||||||
|
pj::MediaFormatAudio mfa;
|
||||||
|
pj::AudioMediaPlayerInfo pinfo;
|
||||||
|
int wavsize;
|
||||||
|
int sleeptime;
|
||||||
|
|
||||||
|
if ( ! pj_file_exists(file.c_str()) ) {
|
||||||
|
communicator.logger.warn("File not found (%s)", file.c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TODO: use some library to get the actual length in millisec
|
||||||
|
*
|
||||||
|
* This just gets the file size and divides by a constant to
|
||||||
|
* estimate the length of the WAVE file in milliseconds.
|
||||||
|
* This depends on the encoding bitrate, etc.
|
||||||
|
*/
|
||||||
|
|
||||||
|
auto ci = getInfo();
|
||||||
|
if (ci.media.size() != 1) {
|
||||||
|
throw sip::Exception("ci.media.size is not 1");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ci.media[0].status == PJSUA_CALL_MEDIA_ACTIVE) {
|
||||||
|
auto *aud_med = static_cast<pj::AudioMedia *>(getMedia(0));
|
||||||
|
|
||||||
|
try {
|
||||||
|
player.createPlayer(file, PJMEDIA_FILE_NO_LOOP);
|
||||||
|
pinfo = player.getInfo();
|
||||||
|
sleeptime = (pinfo.sizeBytes / (pinfo.payloadBitsPerSample * 2.75));
|
||||||
|
|
||||||
|
if ( in_chan ) { // choose the target sound output
|
||||||
|
player.startTransmit(*communicator.calls[ci.id].media);
|
||||||
|
} else {
|
||||||
|
player.startTransmit(*aud_med);
|
||||||
|
}
|
||||||
|
|
||||||
|
pj_thread_sleep(sleeptime);
|
||||||
|
|
||||||
|
if ( in_chan ) { // choose the target sound output
|
||||||
|
player.stopTransmit(*communicator.calls[ci.id].media);
|
||||||
|
} else {
|
||||||
|
player.stopTransmit(*aud_med);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (...) {
|
||||||
|
communicator.logger.notice("Error playing file %s", file.c_str());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
communicator.logger.notice("Call not active - can't play file %s", file.c_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _Call::onDtmfDigit(pj::OnDtmfDigitParam &prm) {
|
void _Call::onDtmfDigit(pj::OnDtmfDigitParam &prm) {
|
||||||
communicator.logger.notice("DTMF digit '%s' (call %d).", prm.digit.c_str(), getId());
|
//communicator.logger.notice("DTMF digit '%s' (call %d).",
|
||||||
|
// prm.digit.c_str(), getId());
|
||||||
|
pj::CallOpParam param;
|
||||||
|
|
||||||
|
auto ci = getInfo();
|
||||||
|
std::string chanName;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* DTMF CALLER MENU
|
||||||
|
*/
|
||||||
|
|
||||||
|
switch ( communicator.dtmf_mode ) {
|
||||||
|
case DTMF_MODE_UNAUTH:
|
||||||
|
/*
|
||||||
|
* IF UNAUTH, the only thing we allow is to authorize.
|
||||||
|
*/
|
||||||
|
switch ( prm.digit[0] ) {
|
||||||
|
case '#':
|
||||||
|
/*
|
||||||
|
* When user presses '#', test PIN entry
|
||||||
|
*/
|
||||||
|
if ( communicator.pins.size() > 0 ) {
|
||||||
|
if ( communicator.pins.count(communicator.got_dtmf) > 0 ) {
|
||||||
|
communicator.logger.info("Caller entered correct PIN");
|
||||||
|
communicator.dtmf_mode = DTMF_MODE_ROOT;
|
||||||
|
communicator.logger.notice("MYDEBUG: %s:%s",
|
||||||
|
communicator.got_dtmf.c_str(),
|
||||||
|
communicator.pins[communicator.got_dtmf].c_str());
|
||||||
|
communicator.calls[ci.id].joinOtherChannel(
|
||||||
|
communicator.pins[communicator.got_dtmf]);
|
||||||
|
|
||||||
|
this->playAudioFile(communicator.file_entering_channel);
|
||||||
|
communicator.calls[ci.id].sendUserState(mumlib::UserState::SELF_MUTE, false);
|
||||||
|
this->playAudioFile(communicator.file_announce_new_caller, true);
|
||||||
|
} else {
|
||||||
|
communicator.logger.info("Caller entered wrong PIN");
|
||||||
|
this->playAudioFile(communicator.file_invalid_pin);
|
||||||
|
if ( communicator.pin_fails++ >= MAX_PIN_FAILS ) {
|
||||||
|
param.statusCode = PJSIP_SC_SERVICE_UNAVAILABLE;
|
||||||
|
pj_thread_sleep(500); // pause before next announcement
|
||||||
|
this->playAudioFile(communicator.file_goodbye);
|
||||||
|
pj_thread_sleep(500); // pause before next announcement
|
||||||
|
this->hangup(param);
|
||||||
|
}
|
||||||
|
this->playAudioFile(communicator.file_prompt_pin);
|
||||||
|
}
|
||||||
|
communicator.got_dtmf = "";
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case '*':
|
||||||
|
/*
|
||||||
|
* Allow user to reset PIN entry by pressing '*'
|
||||||
|
*/
|
||||||
|
communicator.got_dtmf = "";
|
||||||
|
this->playAudioFile(communicator.file_prompt_pin);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
/*
|
||||||
|
* In all other cases, add input digit to stack
|
||||||
|
*/
|
||||||
|
communicator.got_dtmf = communicator.got_dtmf + prm.digit;
|
||||||
|
if ( communicator.got_dtmf.size() > MAX_CALLER_PIN_LEN ) {
|
||||||
|
// just drop 'em if too long
|
||||||
|
param.statusCode = PJSIP_SC_SERVICE_UNAVAILABLE;
|
||||||
|
this->playAudioFile(communicator.file_goodbye);
|
||||||
|
pj_thread_sleep(500); // pause before next announcement
|
||||||
|
this->hangup(param);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case DTMF_MODE_ROOT:
|
||||||
|
/*
|
||||||
|
* User already authenticated; no data entry pending
|
||||||
|
*/
|
||||||
|
switch ( prm.digit[0] ) {
|
||||||
|
case '*':
|
||||||
|
/*
|
||||||
|
* Switch user to 'star' menu
|
||||||
|
*/
|
||||||
|
communicator.dtmf_mode = DTMF_MODE_STAR;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
/*
|
||||||
|
* Default is to ignore all digits in root
|
||||||
|
*/
|
||||||
|
communicator.logger.info("Ignore DTMF digit '%s' in ROOT state", prm.digit.c_str());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case DTMF_MODE_STAR:
|
||||||
|
/*
|
||||||
|
* User already entered '*'; time to perform action
|
||||||
|
*/
|
||||||
|
switch ( prm.digit[0] ) {
|
||||||
|
case '5':
|
||||||
|
// Mute line
|
||||||
|
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.calls[ci.id].sendUserState(mumlib::UserState::SELF_MUTE, false);
|
||||||
|
break;
|
||||||
|
case '9':
|
||||||
|
if ( communicator.pins.size() > 0 ) {
|
||||||
|
communicator.dtmf_mode = DTMF_MODE_UNAUTH;
|
||||||
|
communicator.calls[ci.id].sendUserState(mumlib::UserState::SELF_DEAF, true);
|
||||||
|
communicator.calls[ci.id].joinDefaultChannel();
|
||||||
|
this->playAudioFile(communicator.file_prompt_pin);
|
||||||
|
} else {
|
||||||
|
// we should have a 'not supported' message
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case '0': // block these for the menu itself
|
||||||
|
case '*':
|
||||||
|
default:
|
||||||
|
// play menu
|
||||||
|
communicator.logger.info("Unsupported DTMF digit '%s' in state STAR", prm.digit.c_str());
|
||||||
|
this->playAudioFile(communicator.file_menu);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* In any case, switch back to root after one digit
|
||||||
|
*/
|
||||||
|
communicator.dtmf_mode = DTMF_MODE_ROOT;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
communicator.logger.info("Unexpected DTMF '%s' in unknown state '%d'", prm.digit.c_str(),
|
||||||
|
communicator.dtmf_mode);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _Account::onRegState(pj::OnRegStateParam &prm) {
|
void _Account::onRegState(pj::OnRegStateParam &prm) {
|
||||||
@ -200,10 +444,12 @@ namespace sip {
|
|||||||
|
|
||||||
if (communicator.uriValidator.validateUri(uri)) {
|
if (communicator.uriValidator.validateUri(uri)) {
|
||||||
|
|
||||||
if (available) {
|
if (active_calls < max_calls) {
|
||||||
param.statusCode = PJSIP_SC_OK;
|
param.statusCode = PJSIP_SC_OK;
|
||||||
available = false;
|
active_calls++;
|
||||||
} else {
|
} else {
|
||||||
|
communicator.logger.notice("BUSY - reject incoming call from %s.", uri.c_str());
|
||||||
|
param.statusCode = PJSIP_SC_OK;
|
||||||
param.statusCode = PJSIP_SC_BUSY_EVERYWHERE;
|
param.statusCode = PJSIP_SC_BUSY_EVERYWHERE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -216,18 +462,20 @@ 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));
|
||||||
|
max_calls = maxCalls;
|
||||||
|
|
||||||
|
|
||||||
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;
|
||||||
@ -236,11 +484,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);
|
||||||
}
|
}
|
||||||
@ -271,8 +520,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) {
|
||||||
@ -280,7 +529,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.",
|
||||||
@ -299,9 +549,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;
|
||||||
@ -318,6 +570,7 @@ void sip::PjsuaCommunicator::registerAccount(string host, string user, string pa
|
|||||||
accountConfig.sipConfig.authCreds.push_back(cred);
|
accountConfig.sipConfig.authCreds.push_back(cred);
|
||||||
|
|
||||||
logger.info("Registering account for URI: %s.", uri.c_str());
|
logger.info("Registering account for URI: %s.", uri.c_str());
|
||||||
account.reset(new _Account(*this));
|
account.reset(new _Account(*this, max_calls));
|
||||||
account->create(accountConfig);
|
account->create(accountConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,10 +18,19 @@
|
|||||||
#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};
|
||||||
|
|
||||||
namespace sip {
|
namespace sip {
|
||||||
|
|
||||||
constexpr int DEFAULT_PORT = 5060;
|
constexpr int DEFAULT_PORT = 5060;
|
||||||
constexpr int SAMPLING_RATE = 48000;
|
constexpr int SAMPLING_RATE = 48000;
|
||||||
|
constexpr int MAX_CALLER_PIN_LEN = 64;
|
||||||
|
constexpr int MAX_PIN_FAILS = 2;
|
||||||
|
|
||||||
class Exception : public std::runtime_error {
|
class Exception : public std::runtime_error {
|
||||||
public:
|
public:
|
||||||
@ -58,9 +67,27 @@ 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(mumlib::UserState field, std::string val)> sendUserStateStr;
|
||||||
|
std::function<void()> onConnect;
|
||||||
|
std::function<void()> onDisconnect;
|
||||||
|
std::function<void()> onCallerAuth;
|
||||||
|
std::function<void()> joinAuthChannel; // DEPRECATE ?
|
||||||
|
std::function<void(std::string channelNameRegex)> joinOtherChannel;
|
||||||
|
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,
|
||||||
@ -71,30 +98,44 @@ 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,
|
||||||
unsigned int length);
|
unsigned int length);
|
||||||
|
|
||||||
std::function<void(int16_t *, int)> onIncomingPcmSamples;
|
// config params we get from config.ini
|
||||||
|
std::string caller_pin;
|
||||||
|
std::string file_welcome;
|
||||||
|
std::string file_prompt_pin;
|
||||||
|
std::string file_entering_channel;
|
||||||
|
std::string file_announce_new_caller;
|
||||||
|
std::string file_invalid_pin;
|
||||||
|
std::string file_goodbye;
|
||||||
|
std::string file_mute_on;
|
||||||
|
std::string file_mute_off;
|
||||||
|
std::string file_menu;
|
||||||
|
int max_calls;
|
||||||
|
|
||||||
std::function<void(std::string)> onStateChange;
|
// TODO: move these to private?
|
||||||
|
std::string got_dtmf;
|
||||||
|
dtmf_modes_t dtmf_mode = DTMF_MODE_ROOT;
|
||||||
|
int pin_fails = 0;
|
||||||
|
|
||||||
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];
|
||||||
|
|
||||||
|
std::unordered_map<std::string, std::string> pins;
|
||||||
|
|
||||||
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;
|
||||||
|
|
||||||
|
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
|
./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:
|
||||||
|
|
||||||
|
@ -16,6 +16,10 @@ password = foobar
|
|||||||
# Adjust it if you need to meet the specific bandwidth requirements of Murmur server
|
# Adjust it if you need to meet the specific bandwidth requirements of Murmur server
|
||||||
frameLength = 40
|
frameLength = 40
|
||||||
|
|
||||||
|
# Set the maximum number of SIP calls to allow simultaneously. This should be <= 32.
|
||||||
|
# If you need more, recompile PJSUA LIB and also modify the define in main.hpp.
|
||||||
|
max_calls = 1
|
||||||
|
|
||||||
[mumble]
|
[mumble]
|
||||||
host = example.org
|
host = example.org
|
||||||
port = 64738
|
port = 64738
|
||||||
@ -23,6 +27,37 @@ user = mumsi
|
|||||||
password = foobar
|
password = foobar
|
||||||
channelNameExpression =
|
channelNameExpression =
|
||||||
|
|
||||||
|
# When here is no SIP connection, the mumble state is set to self_mute/self_deaf
|
||||||
|
# so the other users can easily see whether the SIP is connected even when not
|
||||||
|
# in the same group
|
||||||
|
autodeaf = 1
|
||||||
|
|
||||||
# Bitrate of Opus encoder in B/s
|
# Bitrate of Opus encoder in B/s
|
||||||
# Adjust it if you need to meet the specific bandwidth requirements of Murmur server
|
# Adjust it if you need to meet the specific bandwidth requirements of Murmur server
|
||||||
opusEncoderBitrate = 16000
|
opusEncoderBitrate = 16000
|
||||||
|
|
||||||
|
# Set to 1 to use client certificates. The certs must be named <user>-cert.pem and
|
||||||
|
# the private keys <user>-key.pem.
|
||||||
|
use_certs = 0
|
||||||
|
|
||||||
|
[app]
|
||||||
|
|
||||||
|
# Caller PIN needed to authenticate the phone call itself. The caller presses
|
||||||
|
# the PIN, followed by the hash '#' key. On success, the caller is
|
||||||
|
# unmuted/undeafened. On failure, the SIP call is hung up.
|
||||||
|
pin = 4321
|
||||||
|
|
||||||
|
[files]
|
||||||
|
# These files are used for the caller and mumble channel audio clips.
|
||||||
|
# The paths below assume that you are running ./mumsi in the build/ dir.
|
||||||
|
welcome = ../media/welcome.wav
|
||||||
|
prompt_pin = ../media/prompt-pin.wav
|
||||||
|
entering_channel = ../media/entering-channel.wav
|
||||||
|
announce_new_caller = ../media/announce-new-caller.wav
|
||||||
|
invalid_pin = ../media/invalid-pin.wav
|
||||||
|
goodbye = ../media/goodbye.wav
|
||||||
|
mute_on = ../media/mute-on.wav
|
||||||
|
mute_off = ../media/mute-off.wav
|
||||||
|
menu = ../media/menu.wav
|
||||||
|
|
||||||
|
|
||||||
|
211
main.cpp
211
main.cpp
@ -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,36 +52,18 @@ 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"));
|
try {
|
||||||
|
pjsuaCommunicator.pins = conf.getChildren("pins");
|
||||||
using namespace std::placeholders;
|
} catch (...) {
|
||||||
pjsuaCommunicator.onIncomingPcmSamples = std::bind(
|
}
|
||||||
&mumble::MumbleCommunicator::sendPcmSamples,
|
|
||||||
&mumbleCommunicator,
|
|
||||||
_1, _2);
|
|
||||||
|
|
||||||
pjsuaCommunicator.onStateChange = std::bind(
|
|
||||||
&mumble::MumbleCommunicator::sendTextMessage,
|
|
||||||
&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");
|
||||||
@ -85,8 +71,173 @@ int main(int argc, char *argv[]) {
|
|||||||
mumbleConf.user = conf.getString("mumble.user");
|
mumbleConf.user = conf.getString("mumble.user");
|
||||||
mumbleConf.password = conf.getString("mumble.password");
|
mumbleConf.password = conf.getString("mumble.password");
|
||||||
mumbleConf.opusEncoderBitrate = conf.getInt("mumble.opusEncoderBitrate");
|
mumbleConf.opusEncoderBitrate = conf.getInt("mumble.opusEncoderBitrate");
|
||||||
|
/* default to 'false' if not found */
|
||||||
|
try {
|
||||||
|
mumbleConf.autodeaf = conf.getBool("mumble.autodeaf");
|
||||||
|
} catch (...) {
|
||||||
|
mumbleConf.autodeaf = false;
|
||||||
|
}
|
||||||
|
|
||||||
mumbleCommunicator.connect(mumbleConf);
|
try {
|
||||||
|
mumbleConf.comment = conf.getString("app.comment");
|
||||||
|
} catch (...) {
|
||||||
|
mumbleConf.comment = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
try { pjsuaCommunicator.file_welcome = conf.getString("files.welcome");
|
||||||
|
} catch (...) {
|
||||||
|
pjsuaCommunicator.file_welcome = "welcome.wav";
|
||||||
|
}
|
||||||
|
|
||||||
|
try { pjsuaCommunicator.file_prompt_pin = conf.getString("files.prompt_pin");
|
||||||
|
} catch (...) {
|
||||||
|
pjsuaCommunicator.file_prompt_pin = "prompt-pin.wav";
|
||||||
|
}
|
||||||
|
|
||||||
|
try { pjsuaCommunicator.file_entering_channel = conf.getString("files.entering_channel");
|
||||||
|
} catch (...) {
|
||||||
|
pjsuaCommunicator.file_entering_channel = "entering-channel.wav";
|
||||||
|
}
|
||||||
|
|
||||||
|
try { pjsuaCommunicator.file_announce_new_caller = conf.getString("files.announce_new_caller");
|
||||||
|
} catch (...) {
|
||||||
|
pjsuaCommunicator.file_announce_new_caller = "announce-new-caller.wav";
|
||||||
|
}
|
||||||
|
|
||||||
|
try { pjsuaCommunicator.file_invalid_pin = conf.getString("files.invalid_pin");
|
||||||
|
} catch (...) {
|
||||||
|
pjsuaCommunicator.file_invalid_pin = "invalid-pin.wav";
|
||||||
|
}
|
||||||
|
|
||||||
|
try { pjsuaCommunicator.file_goodbye = conf.getString("files.goodbye");
|
||||||
|
} catch (...) {
|
||||||
|
pjsuaCommunicator.file_goodbye = "goodbye.wav";
|
||||||
|
}
|
||||||
|
|
||||||
|
try { pjsuaCommunicator.file_mute_on = conf.getString("files.mute_on");
|
||||||
|
} catch (...) {
|
||||||
|
pjsuaCommunicator.file_mute_on = "mute-on.wav";
|
||||||
|
}
|
||||||
|
|
||||||
|
try { pjsuaCommunicator.file_mute_off = conf.getString("files.mute_off");
|
||||||
|
} catch (...) {
|
||||||
|
pjsuaCommunicator.file_mute_off = "mute-off.wav";
|
||||||
|
}
|
||||||
|
|
||||||
|
try { pjsuaCommunicator.file_menu = conf.getString("files.menu");
|
||||||
|
} catch (...) {
|
||||||
|
pjsuaCommunicator.file_menu = "menu.wav";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string defaultChan = conf.getString("mumble.channelNameExpression");
|
||||||
|
|
||||||
|
mumble::MumbleChannelJoiner mumbleChannelJoiner(defaultChan);
|
||||||
|
mumble::MumbleChannelJoiner mumbleOtherChannelJoiner(defaultChan);
|
||||||
|
|
||||||
|
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(
|
||||||
|
static_cast<void(mumble::MumbleCommunicator::*)(mumlib::UserState, bool)>
|
||||||
|
(&mumble::MumbleCommunicator::sendUserState),
|
||||||
|
mumcom,
|
||||||
|
_1, _2);
|
||||||
|
|
||||||
|
// Send UserState to Mumble
|
||||||
|
pjsuaCommunicator.calls[i].sendUserStateStr = std::bind(
|
||||||
|
static_cast<void(mumble::MumbleCommunicator::*)(mumlib::UserState, std::string)>
|
||||||
|
(&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 to join other channel
|
||||||
|
pjsuaCommunicator.calls[i].joinOtherChannel = std::bind(
|
||||||
|
&mumble::MumbleChannelJoiner::joinOtherChannel,
|
||||||
|
&mumbleOtherChannelJoiner,
|
||||||
|
mumcom,
|
||||||
|
_1);
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
try {
|
||||||
|
if ( conf.getBool("mumble.use_certs") ) {
|
||||||
|
mumbleConf.cert_file = mumbleConf.user + "-cert.pem";
|
||||||
|
mumbleConf.privkey_file = mumbleConf.user + "-key.pem";
|
||||||
|
}
|
||||||
|
} catch (...) {
|
||||||
|
logger.info("Client certs not enabled in config");
|
||||||
|
}
|
||||||
|
mumcom->connect(mumbleConf);
|
||||||
|
}
|
||||||
|
|
||||||
pjsuaCommunicator.connect(
|
pjsuaCommunicator.connect(
|
||||||
conf.getString("sip.host"),
|
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
|
66
make-client-certs.sh
Executable file
66
make-client-certs.sh
Executable file
@ -0,0 +1,66 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# make-client-certs.sh - creates the client certs for registering with Mumble
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
#
|
||||||
|
# make-client-certs.sh <username>
|
||||||
|
#
|
||||||
|
# make-client-certs.sh <userprefix> <count>
|
||||||
|
#
|
||||||
|
# Notes:
|
||||||
|
#
|
||||||
|
# * The certs are self-signed and are not passphrase protected. Depending on
|
||||||
|
# the target environment and usage, this may or may not be OK. If you need
|
||||||
|
# a passphrase, you'll need to hack Mumlib.
|
||||||
|
#
|
||||||
|
# * The names are hard-coded in mumsi to match <username>-key.pem and
|
||||||
|
# <username>-cert.pem. This is done to make it easier to configure multi-line
|
||||||
|
# functionality.
|
||||||
|
#
|
||||||
|
# * When generating files for a series of users, the counter is appended to the
|
||||||
|
# user name, from '0' to one less than the COUNT.
|
||||||
|
|
||||||
|
function usage {
|
||||||
|
cat <<EOF
|
||||||
|
Usage:
|
||||||
|
|
||||||
|
$0 username
|
||||||
|
$0 user-prefix count
|
||||||
|
EOF
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
USER="$1"
|
||||||
|
COUNT="$2"
|
||||||
|
|
||||||
|
# In this 'format', the %s is replaced with the user name generated in
|
||||||
|
# the for loop.
|
||||||
|
SUBJFMT="/C=DE/ST=HE/L=Ffm/O=Mumble Ext./CN=%s"
|
||||||
|
|
||||||
|
if [ -z "$USER" ]; then
|
||||||
|
usage
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$3" ]; then
|
||||||
|
usage
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$COUNT" ]; then
|
||||||
|
COUNT=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
for ((i=0; i<$COUNT; i++)) {
|
||||||
|
prefix="${USER}${i}"
|
||||||
|
subj=$(printf "$SUBJFMT" $prefix)
|
||||||
|
|
||||||
|
openssl req \
|
||||||
|
-nodes \
|
||||||
|
-new \
|
||||||
|
-x509 \
|
||||||
|
-keyout ${prefix}-key.pem \
|
||||||
|
-out ${prefix}-cert.pem \
|
||||||
|
-subj "$subj"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
24
media/Makefile
Normal file
24
media/Makefile
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
# Makefile
|
||||||
|
#
|
||||||
|
# This file generates the WAVE files from text files on macOS
|
||||||
|
#
|
||||||
|
|
||||||
|
WAVES := $(subst .msg,.wav,$(wildcard *.msg)) blow.wav
|
||||||
|
|
||||||
|
VOICE := --voice=Samantha
|
||||||
|
RATE := --rate=15
|
||||||
|
QUALITY := --quality=127
|
||||||
|
|
||||||
|
|
||||||
|
all: $(WAVES)
|
||||||
|
|
||||||
|
%.wav: %.aiff
|
||||||
|
afconvert "$<" -d LEI16 "$@"
|
||||||
|
|
||||||
|
%.aiff: %.msg
|
||||||
|
say $(VOICE) $(RATE) -o $@ < $<
|
||||||
|
|
||||||
|
#announce-new-caller.wav: /System/Library/Sounds/Blow.aiff
|
||||||
|
# afconvert "$<" -d LEI16 "$@"
|
||||||
|
|
||||||
|
|
1
media/announce-new-caller.msg
Normal file
1
media/announce-new-caller.msg
Normal file
@ -0,0 +1 @@
|
|||||||
|
Caller joined
|
BIN
media/announce-new-caller.wav
Normal file
BIN
media/announce-new-caller.wav
Normal file
Binary file not shown.
BIN
media/blow.wav
Normal file
BIN
media/blow.wav
Normal file
Binary file not shown.
1
media/entering-channel.msg
Normal file
1
media/entering-channel.msg
Normal file
@ -0,0 +1 @@
|
|||||||
|
entering channel
|
BIN
media/entering-channel.wav
Normal file
BIN
media/entering-channel.wav
Normal file
Binary file not shown.
1
media/goodbye.msg
Normal file
1
media/goodbye.msg
Normal file
@ -0,0 +1 @@
|
|||||||
|
Goodbye
|
BIN
media/goodbye.wav
Normal file
BIN
media/goodbye.wav
Normal file
Binary file not shown.
1
media/invalid-pin.msg
Normal file
1
media/invalid-pin.msg
Normal file
@ -0,0 +1 @@
|
|||||||
|
Invalid pin
|
BIN
media/invalid-pin.wav
Normal file
BIN
media/invalid-pin.wav
Normal file
Binary file not shown.
3
media/menu.msg
Normal file
3
media/menu.msg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
Press star five to mute.
|
||||||
|
Press star six to un-mute.
|
||||||
|
Press star nine to change channel.
|
BIN
media/menu.wav
Normal file
BIN
media/menu.wav
Normal file
Binary file not shown.
1
media/mute-off.msg
Normal file
1
media/mute-off.msg
Normal file
@ -0,0 +1 @@
|
|||||||
|
mute off
|
BIN
media/mute-off.wav
Normal file
BIN
media/mute-off.wav
Normal file
Binary file not shown.
1
media/mute-on.msg
Normal file
1
media/mute-on.msg
Normal file
@ -0,0 +1 @@
|
|||||||
|
mute on
|
BIN
media/mute-on.wav
Normal file
BIN
media/mute-on.wav
Normal file
Binary file not shown.
1
media/prompt-pin.msg
Normal file
1
media/prompt-pin.msg
Normal file
@ -0,0 +1 @@
|
|||||||
|
Please enter pin, followed by the hash symbol
|
BIN
media/prompt-pin.wav
Normal file
BIN
media/prompt-pin.wav
Normal file
Binary file not shown.
1
media/welcome.msg
Normal file
1
media/welcome.msg
Normal file
@ -0,0 +1 @@
|
|||||||
|
Welcome to the conference bridge
|
BIN
media/welcome.wav
Normal file
BIN
media/welcome.wav
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user