Rewrite Pjsua class to use C++ API.

This commit is contained in:
Michał Słomkowski 2015-11-07 16:32:21 +01:00
parent 3267329a63
commit 904246ba64
2 changed files with 217 additions and 275 deletions

View File

@ -8,117 +8,24 @@
using namespace std; using namespace std;
/** namespace sip {
* These are global, because there's no way to pass it's value to onCallMediaState callback.
*/
//static int mediaPortSlot = -1;
//static sip::PjsuaCommunicator *pjsuaCommunicator = nullptr;
static log4cpp::Category &pjLogger = log4cpp::Category::getInstance("PjSip");
void sip::PjsuaCommunicator::onCallMediaState(pjsua_call_id call_id) {
pjsua_call_info ci;
pjsua_call_get_info(call_id, &ci);
if (ci.media_status == PJSUA_CALL_MEDIA_ACTIVE) {
pjsua_conf_connect(ci.conf_slot, mediaPortSlot);
pjsua_conf_connect(mediaPortSlot, ci.conf_slot);
}
}
void sip::PjsuaCommunicator::onIncomingCall(pjsua_acc_id acc_id, pjsua_call_id call_id, pjsip_rx_data *rdata) {
pjsua_call_info ci;
PJ_UNUSED_ARG(rdata);
pjsua_call_get_info(call_id, &ci);
pjsua_call_set_user_data(call_id, this);
logger.info("Incoming call from %s.", ci.remote_info.ptr);
if (this->available) {
available = false;
pjsua_call_set_user_data(call_id, this);
pjsua_call_answer(call_id, 200, nullptr, nullptr);
} else {
// 486 Busy Here - Callee is Busy
pjsua_call_answer(call_id, 486, nullptr, nullptr);
}
}
void sip::PjsuaCommunicator::onDtmfDigit(pjsua_call_id callId, int digit) {
logger.notice("DTMF digit '%c' (call %d).", digit, callId);
}
void sip::PjsuaCommunicator::onCallState(pjsua_call_id call_id, pjsip_event *e) {
pjsua_call_info ci;
PJ_UNUSED_ARG(e);
pjsua_call_get_info(call_id, &ci);
logger.info("Call %d state=%s.", call_id, ci.state_text.ptr);
string address = string(ci.remote_info.ptr);
boost::replace_all(address, "<", "");
boost::replace_all(address, ">", "");
if (ci.state == PJSIP_INV_STATE_CONFIRMED) {
auto msgText = "Start call from " + address + ".";
logger.notice(msgText);
onStateChange(msgText);
} else if (ci.state == PJSIP_INV_STATE_DISCONNECTED) {
auto msgText = "End call from " + address + ".";
logger.notice(msgText);
onStateChange(msgText);
available = true;
}
}
static void callback_onCallMediaState(pjsua_call_id callId) {
auto *communicator = static_cast<sip::PjsuaCommunicator *>(pjsua_call_get_user_data(callId));
communicator->onCallMediaState(callId);
}
static void callback_onIncomingCall(pjsua_acc_id acc_id, pjsua_call_id call_id, pjsip_rx_data *rdata) {
auto *communicator = static_cast<sip::PjsuaCommunicator *>(pjsua_acc_get_user_data(acc_id));
communicator->onIncomingCall(acc_id, call_id, rdata);
}
static void callback_onDtmfDigit(pjsua_call_id callId, int digit) {
auto *communicator = static_cast<sip::PjsuaCommunicator *>(pjsua_call_get_user_data(callId));
communicator->onDtmfDigit(callId, digit);
}
static void callback_onCallState(pjsua_call_id call_id, pjsip_event *e) {
auto *communicator = static_cast<sip::PjsuaCommunicator *>(pjsua_call_get_user_data(call_id));
communicator->onCallState(call_id, e);
}
static pj_status_t callback_getFrame(pjmedia_port *port, pjmedia_frame *frame) {
auto *communicator = static_cast<sip::PjsuaCommunicator *>(port->port_data.pdata);
return communicator->mediaPortGetFrame(port, frame);
}
static pj_status_t callback_putFrame(pjmedia_port *port, pjmedia_frame *frame) {
auto *communicator = static_cast<sip::PjsuaCommunicator *>(port->port_data.pdata);
return communicator->mediaPortPutFrame(port, frame);
}
static pj_status_t callback_onDestroy(pjmedia_port *port) {
auto *communicator = static_cast<sip::PjsuaCommunicator *>(port->port_data.pdata);
return communicator->_mediaPortOnDestroy(port);
}
static void pjLogToLog4CppBridgeFunction(int level, const char *data, int len) {
using namespace log4cpp; using namespace log4cpp;
class _LogWriter : public pj::LogWriter {
public:
_LogWriter(Category &logger)
: logger(logger) { }
virtual void write(const pj::LogEntry &entry) {
auto message = entry.msg.substr(0, entry.msg.size() - 1); // remove newline
logger << prioritiesMap.at(entry.level) << message;
}
private:
log4cpp::Category &logger;
std::map<int, Priority::Value> prioritiesMap = { std::map<int, Priority::Value> prioritiesMap = {
{1, Priority::ERROR}, {1, Priority::ERROR},
{2, Priority::WARN}, {2, Priority::WARN},
@ -127,118 +34,40 @@ static void pjLogToLog4CppBridgeFunction(int level, const char *data, int len) {
{5, Priority::DEBUG}, {5, Priority::DEBUG},
{6, Priority::DEBUG} {6, Priority::DEBUG}
}; };
};
string message(data); class _MumlibAudioMedia : public pj::AudioMedia {
public:
message = message.substr(0, message.size() - 1); // remove newline _MumlibAudioMedia(sip::PjsuaCommunicator &comm)
: communicator(comm) {
pjLogger << prioritiesMap.at(level) << message;
}
sip::PjsuaCommunicator::PjsuaCommunicator()
: logger(log4cpp::Category::getInstance("SipCommunicator")),
callbackLogger(log4cpp::Category::getInstance("SipCommunicatorCallback")) {
pj_status_t status;
status = pjsua_create();
if (status != PJ_SUCCESS) {
throw sip::Exception("Error in pjsua_create()", status);
}
pj_log_set_log_func(pjLogToLog4CppBridgeFunction);
pjsua_config generalConfig;
pjsua_config_default(&generalConfig);
string userAgent = "Mumsi Mumble-SIP Bridge";
generalConfig.user_agent = toPjString(userAgent);
generalConfig.max_calls = 1;
generalConfig.cb.on_incoming_call = &callback_onIncomingCall;
generalConfig.cb.on_dtmf_digit = &callback_onDtmfDigit;
generalConfig.cb.on_call_media_state = &callback_onCallMediaState;
generalConfig.cb.on_call_state = &callback_onCallState;
pjsua_logging_config logConfig;
pjsua_logging_config_default(&logConfig);
logConfig.cb = pjLogToLog4CppBridgeFunction;
logConfig.console_level = 5;
pjsua_media_config mediaConfig;
pjsua_media_config_default(&mediaConfig);
mediaConfig.no_vad = 1;
status = pjsua_init(&generalConfig, &logConfig, &mediaConfig);
if (status != PJ_SUCCESS) {
throw sip::Exception("error in pjsua_init", status);
}
pj_caching_pool cachingPool;
pj_caching_pool_init(&cachingPool, &pj_pool_factory_default_policy, 0);
pool = pj_pool_create(&cachingPool.factory, "wav", 32768, 8192, nullptr);
// todo calculate sizes
status = pjmedia_circ_buf_create(pool, 960 * 10, &inputBuff);
if (status != PJ_SUCCESS) {
throw sip::Exception("error when creating circular buffer", status);
}
logger.notice("Active ports: %d.", pjsua_conf_get_active_ports());
createMediaPort(); createMediaPort();
registerMediaPort(&mediaPort);
status = pjsua_conf_add_port(pool, &mediaPort, &mediaPortSlot);
if (status != PJ_SUCCESS) {
throw sip::Exception("error when calling pjsua_conf_add_port", status);
}
}
void sip::PjsuaCommunicator::connect(
std::string host,
std::string user,
std::string password,
unsigned int port) {
pj_status_t status;
pjsua_transport_config transportConfig;
pjsua_transport_config_default(&transportConfig);
transportConfig.port = port;
status = pjsua_transport_create(PJSIP_TRANSPORT_UDP, &transportConfig, NULL);
if (status != PJ_SUCCESS) {
throw sip::Exception("error creating transport", status);
} }
status = pjsua_start(); ~_MumlibAudioMedia() {
if (status != PJ_SUCCESS) { unregisterMediaPort();
throw sip::Exception("error starting PJSUA", status);
} }
status = pjsua_set_null_snd_dev(); private:
if (status != PJ_SUCCESS) { pjmedia_port mediaPort;
throw sip::Exception("error in pjsua_set_null_std_dev()", status); sip::PjsuaCommunicator &communicator;
static pj_status_t callback_getFrame(pjmedia_port *port, pjmedia_frame *frame) {
auto *communicator = static_cast<sip::PjsuaCommunicator *>(port->port_data.pdata);
return communicator->mediaPortGetFrame(port, frame);
} }
registerAccount(host, user, password); static pj_status_t callback_putFrame(pjmedia_port *port, pjmedia_frame *frame) {
} auto *communicator = static_cast<sip::PjsuaCommunicator *>(port->port_data.pdata);
return communicator->mediaPortPutFrame(port, frame);
}
sip::PjsuaCommunicator::~PjsuaCommunicator() { void createMediaPort() {
pjsua_destroy();
}
void sip::PjsuaCommunicator::createMediaPort() { auto name = pj_str((char *) "MumsiMediaPort");
eof = false;
string name = "PjsuamediaPort";
auto pjName = toPjString(name);
pj_status_t status = pjmedia_port_info_init(&(mediaPort.info), pj_status_t status = pjmedia_port_info_init(&(mediaPort.info),
&pjName, &name,
PJMEDIA_SIG_CLASS_PORT_AUD('s', 'i'), PJMEDIA_SIG_CLASS_PORT_AUD('s', 'i'),
SAMPLING_RATE, SAMPLING_RATE,
1, 1,
@ -247,23 +76,167 @@ void sip::PjsuaCommunicator::createMediaPort() {
1000); // todo recalculate to match mumble specs 1000); // todo recalculate to match mumble specs
if (status != PJ_SUCCESS) { if (status != PJ_SUCCESS) {
throw sip::Exception("error while calling pjmedia_port_info_init().", status); throw sip::Exception("error while calling pjmedia_port_info_init()", status);
} }
mediaPort.port_data.pdata = this; mediaPort.port_data.pdata = &communicator;
mediaPort.get_frame = &callback_getFrame; mediaPort.get_frame = &callback_getFrame;
mediaPort.put_frame = &callback_putFrame; mediaPort.put_frame = &callback_putFrame;
mediaPort.on_destroy = &callback_onDestroy; }
};
class _Call : public pj::Call {
public:
_Call(sip::PjsuaCommunicator &comm, pj::Account &acc, int call_id = PJSUA_INVALID_ID)
: pj::Call(acc, call_id),
communicator(comm),
account(acc) { }
virtual void onCallState(pj::OnCallStateParam &prm) {
auto ci = getInfo();
communicator.logger.info("Call %d state=%s.", ci.id, ci.stateText.c_str());
string address = ci.remoteUri;
boost::replace_all(address, "<", "");
boost::replace_all(address, ">", "");
if (ci.state == PJSIP_INV_STATE_CONFIRMED) {
auto msgText = "Incoming call from " + address + ".";
communicator.logger.notice(msgText);
communicator.onStateChange(msgText);
} else if (ci.state == PJSIP_INV_STATE_DISCONNECTED) {
auto msgText = "Call from " + address + " finished.";
communicator.logger.notice(msgText);
communicator.onStateChange(msgText);
delete this;
}
}
virtual void onCallMediaState(pj::OnCallMediaStateParam &prm) {
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));
communicator.media->startTransmit(*aud_med);
aud_med->startTransmit(*communicator.media);
}
}
virtual void onDtmfDigit(pj::OnDtmfDigitParam &prm) {
communicator.logger.notice("DTMF digit '%s' (call %d).", prm.digit.c_str(), getId());
}
private:
sip::PjsuaCommunicator &communicator;
pj::Account &account;
};
class _Account : public pj::Account {
public:
_Account(sip::PjsuaCommunicator &comm)
: communicator(comm) { }
virtual void onRegState(pj::OnRegStateParam &prm) {
pj::AccountInfo ai = getInfo();
communicator.logger << log4cpp::Priority::INFO
<< (ai.regIsActive ? "Register:" : "Unregister:") << " code=" << prm.code;
}
virtual void onIncomingCall(pj::OnIncomingCallParam &iprm) {
auto *call = new _Call(communicator, *this, iprm.callId);
communicator.logger.info("Incoming call from %s.", call->getInfo().remoteUri.c_str());
pj::CallOpParam param;
param.statusCode = PJSIP_SC_OK;
call->answer(param);
}
private:
sip::PjsuaCommunicator &communicator;
friend class _Call;
};
} }
sip::PjsuaCommunicator::PjsuaCommunicator()
: logger(log4cpp::Category::getInstance("SipCommunicator")),
pjsuaLogger(log4cpp::Category::getInstance("Pjsua")) {
logWriter.reset(new sip::_LogWriter(pjsuaLogger));
endpoint.libCreate();
pj::EpConfig endpointConfig;
endpointConfig.uaConfig.userAgent = "Mumsi Mumble-SIP gateway";
endpointConfig.uaConfig.maxCalls = 1;
endpointConfig.logConfig.writer = logWriter.get();
endpointConfig.logConfig.level = 5;
endpointConfig.medConfig.noVad = true;
endpoint.libInit(endpointConfig);
pj_status_t status;
pj_caching_pool cachingPool;
pj_caching_pool_init(&cachingPool, &pj_pool_factory_default_policy, 0);
pool = pj_pool_create(&cachingPool.factory, "media", 32768, 8192, nullptr);
if (!pool) {
throw sip::Exception("error when creating memory pool", status);
}
// todo calculate sizes
status = pjmedia_circ_buf_create(pool, 960 * 10, &inputBuff);
if (status != PJ_SUCCESS) {
throw sip::Exception("error when creating circular buffer", status);
}
media.reset(new _MumlibAudioMedia(*this));
}
void sip::PjsuaCommunicator::connect(
std::string host,
std::string user,
std::string password,
unsigned int port) {
pj::TransportConfig transportConfig;
transportConfig.port = port;
endpoint.transportCreate(PJSIP_TRANSPORT_UDP, transportConfig); // todo try catch
endpoint.libStart();
pj_status_t status = pjsua_set_null_snd_dev();
if (status != PJ_SUCCESS) {
throw sip::Exception("error in pjsua_set_null_std_dev()", status);
}
registerAccount(host, user, password);
}
sip::PjsuaCommunicator::~PjsuaCommunicator() {
endpoint.libDestroy();
}
pj_status_t sip::PjsuaCommunicator::mediaPortGetFrame(pjmedia_port *port, pjmedia_frame *frame) { pj_status_t sip::PjsuaCommunicator::mediaPortGetFrame(pjmedia_port *port, pjmedia_frame *frame) {
std::unique_lock<std::mutex> lock(inBuffAccessMutex); std::unique_lock<std::mutex> lock(inBuffAccessMutex);
if (this->eof) {
return PJ_EEOF;
}
frame->type = PJMEDIA_FRAME_TYPE_AUDIO; frame->type = PJMEDIA_FRAME_TYPE_AUDIO;
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));
@ -271,11 +244,11 @@ pj_status_t sip::PjsuaCommunicator::mediaPortGetFrame(pjmedia_port *port, pjmedi
pj_size_t availableSamples = pjmedia_circ_buf_get_len(inputBuff); pj_size_t availableSamples = pjmedia_circ_buf_get_len(inputBuff);
const int samplesToRead = std::min(count, availableSamples); const int samplesToRead = std::min(count, availableSamples);
callbackLogger.debug("Pulling %d samples from in-buff.", samplesToRead); pjsuaLogger.debug("Pulling %d samples from in-buff.", samplesToRead);
pjmedia_circ_buf_read(inputBuff, samples, samplesToRead); pjmedia_circ_buf_read(inputBuff, samples, samplesToRead);
if (availableSamples < count) { if (availableSamples < count) {
callbackLogger.debug("Requested %d samples, available %d, filling remaining with zeros.", count, pjsuaLogger.debug("Requested %d samples, available %d, filling remaining with zeros.", count,
availableSamples); availableSamples);
for (int i = samplesToRead; i < count; ++i) { for (int i = samplesToRead; i < count; ++i) {
@ -287,67 +260,36 @@ pj_status_t sip::PjsuaCommunicator::mediaPortGetFrame(pjmedia_port *port, pjmedi
} }
pj_status_t sip::PjsuaCommunicator::mediaPortPutFrame(pjmedia_port *port, pjmedia_frame *frame) { pj_status_t sip::PjsuaCommunicator::mediaPortPutFrame(pjmedia_port *port, pjmedia_frame *frame) {
if (this->eof) {
return PJ_EEOF;
}
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);
frame->type = PJMEDIA_FRAME_TYPE_AUDIO; frame->type = PJMEDIA_FRAME_TYPE_AUDIO;
if (count > 0) { if (count > 0) {
callbackLogger.debug("Calling onIncomingPcmSamples with %d samples.", count); pjsuaLogger.debug("Calling onIncomingPcmSamples with %d samples.", count);
onIncomingPcmSamples(samples, count); onIncomingPcmSamples(samples, count);
} }
return PJ_SUCCESS; return PJ_SUCCESS;
} }
pj_status_t sip::PjsuaCommunicator::_mediaPortOnDestroy(pjmedia_port *port) {
eof = true;
return PJ_SUCCESS;
}
void sip::PjsuaCommunicator::registerAccount(string host, string user, string password) { void sip::PjsuaCommunicator::registerAccount(string host, string user, string password) {
pjsua_acc_config accConfig; string uri = "sip:" + user + "@" + host;
pjsua_acc_config_default(&accConfig); pj::AccountConfig accountConfig;
accountConfig.idUri = uri;
accountConfig.regConfig.registrarUri = "sip:" + host;
string uri = string("sip:") + user + "@" + host; pj::AuthCredInfo cred("digest", "*", user, 0, password);
string regUri = "sip:" + host; accountConfig.sipConfig.authCreds.push_back(cred);
string scheme = "digest";
pj_status_t status;
status = pjsua_verify_sip_url(uri.c_str());
if (status != PJ_SUCCESS) {
throw sip::Exception("invalid URI format", status);
}
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));
accConfig.id = toPjString(uri); account->create(accountConfig);
accConfig.reg_uri = toPjString(regUri);
accConfig.cred_count = 1;
accConfig.cred_info[0].realm = toPjString(host);
accConfig.cred_info[0].scheme = toPjString(scheme);
accConfig.cred_info[0].username = toPjString(user);
accConfig.cred_info[0].data_type = PJSIP_CRED_DATA_PLAIN_PASSWD;
accConfig.cred_info[0].data = toPjString(password);
accConfig.user_data = this;
pjsua_acc_id acc_id;
status = pjsua_acc_add(&accConfig, PJ_TRUE, &acc_id);
if (status != PJ_SUCCESS) {
throw sip::Exception("failed to register account", status);
}
} }
void sip::PjsuaCommunicator::sendPcmSamples(int16_t *samples, unsigned int length) { void sip::PjsuaCommunicator::sendPcmSamples(int16_t *samples, unsigned int length) {
std::unique_lock<std::mutex> lock(inBuffAccessMutex); std::unique_lock<std::mutex> lock(inBuffAccessMutex);
callbackLogger.debug("Pushing %d samples to in-buff.", length); pjsuaLogger.debug("Pushing %d samples to in-buff.", length);
pjmedia_circ_buf_write(inputBuff, samples, length); pjmedia_circ_buf_write(inputBuff, samples, length);
} }

View File

@ -5,6 +5,8 @@
#include <pjmedia.h> #include <pjmedia.h>
#include <pjsua-lib/pjsua.h> #include <pjsua-lib/pjsua.h>
#include <pjsua2.hpp>
#undef isblank #undef isblank
#include <log4cpp/Category.hh> #include <log4cpp/Category.hh>
@ -14,6 +16,7 @@
#include <stdexcept> #include <stdexcept>
#include <mutex> #include <mutex>
#include <climits> #include <climits>
#include <bits/unique_ptr.h>
namespace sip { namespace sip {
@ -43,9 +46,13 @@ namespace sip {
std::string mesg; std::string mesg;
}; };
inline pj_str_t toPjString(std::string &str) { class _LogWriter;
return pj_str(const_cast<char *>(str.c_str()));
} class _Account;
class _Call;
class _MumlibAudioMedia;
class PjsuaCommunicator : public ICommunicator, boost::noncopyable { class PjsuaCommunicator : public ICommunicator, boost::noncopyable {
public: public:
@ -63,27 +70,19 @@ namespace sip {
std::function<void(std::string)> onStateChange; std::function<void(std::string)> onStateChange;
void onCallMediaState(pjsua_call_id call_id);
void onIncomingCall(pjsua_acc_id acc_id, pjsua_call_id call_id, pjsip_rx_data *rdata);
void onDtmfDigit(pjsua_call_id callId, int digit);
void onCallState(pjsua_call_id call_id, pjsip_event *e);
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);
pj_status_t _mediaPortOnDestroy(pjmedia_port *port);
private: private:
log4cpp::Category &logger; log4cpp::Category &logger;
log4cpp::Category &callbackLogger; log4cpp::Category &pjsuaLogger;
pjmedia_port mediaPort; std::unique_ptr<_LogWriter> logWriter;
int mediaPortSlot = INT_MIN; std::unique_ptr<_Account> account;
bool eof = false; std::unique_ptr<_MumlibAudioMedia> media;
pj::Endpoint endpoint;
pj_pool_t *pool = nullptr; pj_pool_t *pool = nullptr;
@ -91,14 +90,15 @@ namespace sip {
std::mutex inBuffAccessMutex; std::mutex inBuffAccessMutex;
bool available = true;
void createMediaPort();
void registerAccount(std::string host, void registerAccount(std::string host,
std::string user, std::string user,
std::string password); std::string password);
friend class _Call;
friend class _Account;
friend class _MumlibAudioMedia;
}; };
} }