PjsuaCommunicator.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525
  1. #include "PjsuaCommunicator.hpp"
  2. #include <pjlib.h>
  3. #include <pjsua-lib/pjsua.h>
  4. #include <boost/algorithm/string.hpp>
  5. #include <boost/format.hpp>
  6. using namespace std;
  7. namespace sip {
  8. using namespace log4cpp;
  9. class _LogWriter : public pj::LogWriter {
  10. public:
  11. _LogWriter(Category &logger)
  12. : logger(logger) { }
  13. virtual void write(const pj::LogEntry &entry) override {
  14. auto message = entry.msg.substr(0, entry.msg.size() - 1); // remove newline
  15. logger << prioritiesMap.at(entry.level) << message;
  16. }
  17. private:
  18. log4cpp::Category &logger;
  19. std::map<int, Priority::Value> prioritiesMap = {
  20. {1, Priority::ERROR},
  21. {2, Priority::WARN},
  22. {3, Priority::NOTICE},
  23. {4, Priority::INFO},
  24. {5, Priority::DEBUG},
  25. {6, Priority::DEBUG}
  26. };
  27. };
  28. class _MumlibAudioMedia : public pj::AudioMedia {
  29. public:
  30. _MumlibAudioMedia(sip::PjsuaCommunicator &comm, int frameTimeLength)
  31. : communicator(comm) {
  32. createMediaPort(frameTimeLength);
  33. registerMediaPort(&mediaPort);
  34. }
  35. ~_MumlibAudioMedia() {
  36. unregisterMediaPort();
  37. }
  38. private:
  39. pjmedia_port mediaPort;
  40. sip::PjsuaCommunicator &communicator;
  41. static pj_status_t callback_getFrame(pjmedia_port *port, pjmedia_frame *frame) {
  42. auto *communicator = static_cast<sip::PjsuaCommunicator *>(port->port_data.pdata);
  43. return communicator->mediaPortGetFrame(port, frame);
  44. }
  45. static pj_status_t callback_putFrame(pjmedia_port *port, pjmedia_frame *frame) {
  46. auto *communicator = static_cast<sip::PjsuaCommunicator *>(port->port_data.pdata);
  47. return communicator->mediaPortPutFrame(port, frame);
  48. }
  49. void createMediaPort(int frameTimeLength) {
  50. auto name = pj_str((char *) "MumsiMediaPort");
  51. if (frameTimeLength != 10
  52. and frameTimeLength != 20
  53. and frameTimeLength != 40
  54. and frameTimeLength != 60) {
  55. throw sip::Exception(
  56. (boost::format("valid frame time length value: %d. valid values are: 10, 20, 40, 60") %
  57. frameTimeLength).str());
  58. }
  59. pj_status_t status = pjmedia_port_info_init(&(mediaPort.info),
  60. &name,
  61. PJMEDIA_SIG_CLASS_PORT_AUD('s', 'i'),
  62. SAMPLING_RATE,
  63. 1,
  64. 16,
  65. SAMPLING_RATE * frameTimeLength / 1000);
  66. if (status != PJ_SUCCESS) {
  67. throw sip::Exception("error while calling pjmedia_port_info_init()", status);
  68. }
  69. mediaPort.port_data.pdata = &communicator;
  70. mediaPort.get_frame = &callback_getFrame;
  71. mediaPort.put_frame = &callback_putFrame;
  72. }
  73. };
  74. class _Call : public pj::Call {
  75. public:
  76. _Call(sip::PjsuaCommunicator &comm, pj::Account &acc, int call_id = PJSUA_INVALID_ID)
  77. : pj::Call(acc, call_id),
  78. communicator(comm),
  79. account(acc) { }
  80. virtual void onCallState(pj::OnCallStateParam &prm) override;
  81. virtual void onCallMediaState(pj::OnCallMediaStateParam &prm) override;
  82. virtual void onDtmfDigit(pj::OnDtmfDigitParam &prm) override;
  83. virtual void playAudioFile(std::string file);
  84. virtual void playAudioFile(std::string file, bool in_chan);
  85. private:
  86. sip::PjsuaCommunicator &communicator;
  87. pj::Account &account;
  88. };
  89. class _Account : public pj::Account {
  90. public:
  91. _Account(sip::PjsuaCommunicator &comm)
  92. : communicator(comm) { }
  93. virtual void onRegState(pj::OnRegStateParam &prm) override;
  94. virtual void onIncomingCall(pj::OnIncomingCallParam &iprm) override;
  95. private:
  96. sip::PjsuaCommunicator &communicator;
  97. bool available = true;
  98. friend class _Call;
  99. };
  100. void _Call::onCallState(pj::OnCallStateParam &prm) {
  101. auto ci = getInfo();
  102. communicator.logger.info("Call %d state=%s.", ci.id, ci.stateText.c_str());
  103. string address = ci.remoteUri;
  104. boost::replace_all(address, "<", "");
  105. boost::replace_all(address, ">", "");
  106. if (ci.state == PJSIP_INV_STATE_CONFIRMED) {
  107. auto msgText = "Incoming call from " + address + ".";
  108. communicator.logger.notice(msgText);
  109. communicator.onStateChange(msgText);
  110. pj_thread_sleep(500); // sleep a moment to allow connection to stabilize
  111. this->playAudioFile(communicator.file_welcome);
  112. communicator.got_dtmf = "";
  113. /*
  114. * if no pin is set, go ahead and turn off mute/deaf
  115. * otherwise, wait for pin to be entered
  116. */
  117. if ( communicator.caller_pin.length() == 0 ) {
  118. // No PIN set... enter DTMF root menu and turn off mute/deaf
  119. communicator.dtmf_mode = DTMF_MODE_ROOT;
  120. communicator.onMuteDeafChange(0);
  121. } else {
  122. // PIN set... enter DTMF unauth menu and play PIN prompt message
  123. communicator.dtmf_mode = DTMF_MODE_UNAUTH;
  124. pj_thread_sleep(500); // pause briefly after announcement
  125. this->playAudioFile(communicator.file_prompt_pin);
  126. }
  127. } else if (ci.state == PJSIP_INV_STATE_DISCONNECTED) {
  128. auto &acc = dynamic_cast<_Account &>(account);
  129. if (not acc.available) {
  130. auto msgText = "Call from " + address + " finished.";
  131. communicator.mixer->clear();
  132. communicator.logger.notice(msgText);
  133. communicator.onStateChange(msgText);
  134. communicator.onMuteDeafChange(1);
  135. acc.available = true;
  136. }
  137. delete this;
  138. }
  139. }
  140. void _Call::onCallMediaState(pj::OnCallMediaStateParam &prm) {
  141. auto ci = getInfo();
  142. if (ci.media.size() != 1) {
  143. throw sip::Exception("ci.media.size is not 1");
  144. }
  145. if (ci.media[0].status == PJSUA_CALL_MEDIA_ACTIVE) {
  146. auto *aud_med = static_cast<pj::AudioMedia *>(getMedia(0));
  147. communicator.media->startTransmit(*aud_med);
  148. aud_med->startTransmit(*communicator.media);
  149. } else if (ci.media[0].status == PJSUA_CALL_MEDIA_NONE) {
  150. dynamic_cast<_Account &>(account).available = true;
  151. }
  152. }
  153. void _Call::playAudioFile(std::string file) {
  154. this->playAudioFile(file, false); // default is NOT to echo to mumble
  155. }
  156. /* TODO:
  157. * - local deafen before playing and undeafen after?
  158. */
  159. void _Call::playAudioFile(std::string file, bool in_chan) {
  160. communicator.logger.notice("Entered playAudioFile(%s)", file.c_str());
  161. pj::AudioMediaPlayer player;
  162. pj::MediaFormatAudio mfa;
  163. pj::AudioMediaPlayerInfo pinfo;
  164. int wavsize;
  165. int sleeptime;
  166. if ( ! pj_file_exists(file.c_str()) ) {
  167. communicator.logger.warn("File not found (%s)", file.c_str());
  168. return;
  169. }
  170. /* TODO: use some library to get the actual length in millisec
  171. *
  172. * This just gets the file size and divides by a constant to
  173. * estimate the length of the WAVE file in milliseconds.
  174. * This depends on the encoding bitrate, etc.
  175. */
  176. auto ci = getInfo();
  177. if (ci.media.size() != 1) {
  178. throw sip::Exception("ci.media.size is not 1");
  179. }
  180. if (ci.media[0].status == PJSUA_CALL_MEDIA_ACTIVE) {
  181. auto *aud_med = static_cast<pj::AudioMedia *>(getMedia(0));
  182. try {
  183. player.createPlayer(file, PJMEDIA_FILE_NO_LOOP);
  184. pinfo = player.getInfo();
  185. sleeptime = pinfo.sizeBytes / (pinfo.payloadBitsPerSample * 3);
  186. /*
  187. communicator.logger.notice("DEBUG: wavsize=%d pbps=%d bytes=%d samples=%d",
  188. wavsize, pinfo.payloadBitsPerSample, pinfo.sizeBytes, pinfo.sizeSamples);
  189. communicator.logger.notice("WAVE length in ms: %d", sleeptime);
  190. */
  191. if ( in_chan ) { // choose the target sound output
  192. player.startTransmit(*communicator.media);
  193. } else {
  194. player.startTransmit(*aud_med);
  195. }
  196. pj_thread_sleep(sleeptime);
  197. if ( in_chan ) { // choose the target sound output
  198. player.stopTransmit(*communicator.media);
  199. } else {
  200. player.stopTransmit(*aud_med);
  201. }
  202. } catch (...) {
  203. communicator.logger.notice("Error playing file %s", file.c_str());
  204. }
  205. } else {
  206. communicator.logger.notice("Call not active - can't play file %s", file.c_str());
  207. }
  208. }
  209. void _Call::onDtmfDigit(pj::OnDtmfDigitParam &prm) {
  210. //communicator.logger.notice("DTMF digit '%s' (call %d).",
  211. // prm.digit.c_str(), getId());
  212. pj::CallOpParam param;
  213. /*
  214. * DTMF CALLER MENU
  215. */
  216. switch ( communicator.dtmf_mode ) {
  217. case DTMF_MODE_UNAUTH:
  218. /*
  219. * IF UNAUTH, the only thing we allow is to authorize.
  220. */
  221. switch ( prm.digit[0] ) {
  222. case '#':
  223. /*
  224. * When user presses '#', test PIN entry
  225. */
  226. if ( communicator.caller_pin.length() > 0 ) {
  227. if ( communicator.got_dtmf == communicator.caller_pin ) {
  228. communicator.logger.notice("Caller entered correct PIN");
  229. communicator.dtmf_mode = DTMF_MODE_ROOT;
  230. this->playAudioFile(communicator.file_entering_channel);
  231. communicator.onMuteDeafChange(0);
  232. this->playAudioFile(communicator.file_announce_new_caller, true);
  233. } else {
  234. communicator.logger.notice("Caller entered wrong PIN");
  235. this->playAudioFile(communicator.file_invalid_pin);
  236. if ( communicator.pin_fails++ >= MAX_PIN_FAILS ) {
  237. param.statusCode = PJSIP_SC_SERVICE_UNAVAILABLE;
  238. pj_thread_sleep(500); // pause before next announcement
  239. this->playAudioFile(communicator.file_goodbye);
  240. pj_thread_sleep(500); // pause before next announcement
  241. this->hangup(param);
  242. }
  243. this->playAudioFile(communicator.file_prompt_pin);
  244. }
  245. communicator.got_dtmf = "";
  246. }
  247. break;
  248. case '*':
  249. /*
  250. * Allow user to reset PIN entry by pressing '*'
  251. */
  252. communicator.got_dtmf = "";
  253. this->playAudioFile(communicator.file_prompt_pin);
  254. break;
  255. default:
  256. /*
  257. * In all other cases, add input digit to stack
  258. */
  259. communicator.got_dtmf = communicator.got_dtmf + prm.digit;
  260. if ( communicator.got_dtmf.size() > MAX_CALLER_PIN_LEN ) {
  261. // just drop 'em if too long
  262. param.statusCode = PJSIP_SC_SERVICE_UNAVAILABLE;
  263. this->playAudioFile(communicator.file_goodbye);
  264. pj_thread_sleep(500); // pause before next announcement
  265. this->hangup(param);
  266. }
  267. }
  268. break;
  269. case DTMF_MODE_ROOT:
  270. /*
  271. * User already authenticated; no data entry pending
  272. */
  273. switch ( prm.digit[0] ) {
  274. case '*':
  275. /*
  276. * Switch user to 'star' menu
  277. */
  278. communicator.dtmf_mode = DTMF_MODE_STAR;
  279. break;
  280. default:
  281. /*
  282. * Default is to ignore all digits in root
  283. */
  284. communicator.logger.notice("Ignore DTMF digit '%s' in ROOT state", prm.digit.c_str());
  285. }
  286. break;
  287. case DTMF_MODE_STAR:
  288. /*
  289. * User already entered '*'; time to perform action
  290. */
  291. switch ( prm.digit[0] ) {
  292. /*
  293. case '5':
  294. // Mute line
  295. communicator.onMuteChange(1);
  296. this->playAudioFile(communicator.file_mute_on);
  297. break;
  298. case '6':
  299. // Un-mute line
  300. this->playAudioFile(communicator.file_mute_off);
  301. communicator.onMuteChange(0);
  302. break;
  303. */
  304. default:
  305. communicator.logger.notice("Unsupported DTMF digit '%s' in state STAR", prm.digit.c_str());
  306. }
  307. /*
  308. * In any case, switch back to root after one digit
  309. */
  310. communicator.dtmf_mode = DTMF_MODE_ROOT;
  311. break;
  312. default:
  313. communicator.logger.notice("Unexpected DTMF '%s' in unknown state '%d'", prm.digit.c_str(),
  314. communicator.dtmf_mode);
  315. }
  316. }
  317. void _Account::onRegState(pj::OnRegStateParam &prm) {
  318. pj::AccountInfo ai = getInfo();
  319. communicator.logger << log4cpp::Priority::INFO
  320. << (ai.regIsActive ? "Register:" : "Unregister:") << " code=" << prm.code;
  321. }
  322. void _Account::onIncomingCall(pj::OnIncomingCallParam &iprm) {
  323. auto *call = new _Call(communicator, *this, iprm.callId);
  324. string uri = call->getInfo().remoteUri;
  325. communicator.logger.info("Incoming call from %s.", uri.c_str());
  326. pj::CallOpParam param;
  327. if (communicator.uriValidator.validateUri(uri)) {
  328. if (available) {
  329. param.statusCode = PJSIP_SC_OK;
  330. available = false;
  331. } else {
  332. param.statusCode = PJSIP_SC_BUSY_EVERYWHERE;
  333. }
  334. call->answer(param);
  335. } else {
  336. communicator.logger.warn("Refusing call from %s.", uri.c_str());
  337. param.statusCode = PJSIP_SC_SERVICE_UNAVAILABLE;
  338. call->hangup(param);
  339. }
  340. }
  341. }
  342. sip::PjsuaCommunicator::PjsuaCommunicator(IncomingConnectionValidator &validator, int frameTimeLength)
  343. : logger(log4cpp::Category::getInstance("SipCommunicator")),
  344. pjsuaLogger(log4cpp::Category::getInstance("Pjsua")),
  345. uriValidator(validator) {
  346. logWriter.reset(new sip::_LogWriter(pjsuaLogger));
  347. endpoint.libCreate();
  348. pj::EpConfig endpointConfig;
  349. endpointConfig.uaConfig.userAgent = "Mumsi Mumble-SIP gateway";
  350. endpointConfig.uaConfig.maxCalls = 1;
  351. endpointConfig.logConfig.writer = logWriter.get();
  352. endpointConfig.logConfig.level = 5;
  353. endpointConfig.medConfig.noVad = true;
  354. endpoint.libInit(endpointConfig);
  355. pj_caching_pool_init(&cachingPool, &pj_pool_factory_default_policy, 0);
  356. mixer.reset(new mixer::AudioFramesMixer(cachingPool.factory));
  357. media.reset(new _MumlibAudioMedia(*this, frameTimeLength));
  358. logger.info("Created Pjsua communicator with frame length %d ms.", frameTimeLength);
  359. }
  360. void sip::PjsuaCommunicator::connect(
  361. std::string host,
  362. std::string user,
  363. std::string password,
  364. unsigned int port) {
  365. pj::TransportConfig transportConfig;
  366. transportConfig.port = port;
  367. endpoint.transportCreate(PJSIP_TRANSPORT_UDP, transportConfig); // todo try catch
  368. endpoint.libStart();
  369. pj_status_t status = pjsua_set_null_snd_dev();
  370. if (status != PJ_SUCCESS) {
  371. throw sip::Exception("error in pjsua_set_null_std_dev()", status);
  372. }
  373. registerAccount(host, user, password);
  374. }
  375. sip::PjsuaCommunicator::~PjsuaCommunicator() {
  376. endpoint.libDestroy();
  377. }
  378. void sip::PjsuaCommunicator::sendPcmSamples(int sessionId, int sequenceNumber, int16_t *samples, unsigned int length) {
  379. mixer->addFrameToBuffer(sessionId, sequenceNumber, samples, length);
  380. }
  381. pj_status_t sip::PjsuaCommunicator::mediaPortGetFrame(pjmedia_port *port, pjmedia_frame *frame) {
  382. frame->type = PJMEDIA_FRAME_TYPE_AUDIO;
  383. pj_int16_t *samples = static_cast<pj_int16_t *>(frame->buf);
  384. pj_size_t count = frame->size / 2 / PJMEDIA_PIA_CCNT(&(port->info));
  385. const int readSamples = mixer->getMixedSamples(samples, count);
  386. if (readSamples < count) {
  387. pjsuaLogger.debug("Requested %d samples, available %d, filling remaining with zeros.",
  388. count, readSamples);
  389. for (int i = readSamples; i < count; ++i) {
  390. samples[i] = 0;
  391. }
  392. }
  393. return PJ_SUCCESS;
  394. }
  395. pj_status_t sip::PjsuaCommunicator::mediaPortPutFrame(pjmedia_port *port, pjmedia_frame *frame) {
  396. pj_int16_t *samples = static_cast<pj_int16_t *>(frame->buf);
  397. pj_size_t count = frame->size / 2 / PJMEDIA_PIA_CCNT(&port->info);
  398. frame->type = PJMEDIA_FRAME_TYPE_AUDIO;
  399. if (count > 0) {
  400. pjsuaLogger.debug("Calling onIncomingPcmSamples with %d samples.", count);
  401. onIncomingPcmSamples(samples, count);
  402. }
  403. return PJ_SUCCESS;
  404. }
  405. void sip::PjsuaCommunicator::registerAccount(string host, string user, string password) {
  406. string uri = "sip:" + user + "@" + host;
  407. pj::AccountConfig accountConfig;
  408. accountConfig.idUri = uri;
  409. accountConfig.regConfig.registrarUri = "sip:" + host;
  410. pj::AuthCredInfo cred("digest", "*", user, 0, password);
  411. accountConfig.sipConfig.authCreds.push_back(cred);
  412. logger.info("Registering account for URI: %s.", uri.c_str());
  413. account.reset(new _Account(*this));
  414. account->create(accountConfig);
  415. }