PjsuaCommunicator.cpp 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  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. /**
  8. * These are global, because there's no way to pass it's value to onCallMediaState callback.
  9. */
  10. static int mediaPortSlot = -1;
  11. static sip::PjsuaCommunicator *pjsuaCommunicator = nullptr;
  12. static log4cpp::Category &pjLogger = log4cpp::Category::getInstance("PjSip");
  13. void sip::PjsuaCommunicator_onCallMediaState(pjsua_call_id call_id) {
  14. pjsua_call_info ci;
  15. pjsua_call_get_info(call_id, &ci);
  16. if (ci.media_status == PJSUA_CALL_MEDIA_ACTIVE) {
  17. if (mediaPortSlot == -1) {
  18. pj_status_t status = pjsua_conf_add_port(
  19. pjsuaCommunicator->pool,
  20. pjsuaCommunicator->mediaPort,
  21. &mediaPortSlot);
  22. if (status != PJ_SUCCESS) {
  23. throw sip::Exception("error when calling pjsua_conf_add_port", status);
  24. }
  25. }
  26. pjsua_conf_connect(ci.conf_slot, mediaPortSlot);
  27. pjsua_conf_connect(mediaPortSlot, ci.conf_slot);
  28. }
  29. }
  30. static void onIncomingCall(pjsua_acc_id acc_id,
  31. pjsua_call_id call_id,
  32. pjsip_rx_data *rdata) {
  33. pjsua_call_info ci;
  34. PJ_UNUSED_ARG(acc_id);
  35. PJ_UNUSED_ARG(rdata);
  36. pjsua_call_get_info(call_id, &ci);
  37. pjsua_call_set_user_data(call_id, pjsuaCommunicator);
  38. pjLogger.info("Incoming call from %s.", ci.remote_info.ptr);
  39. /* Automatically answer incoming calls with 200/OK */
  40. pjsua_call_answer(call_id, 200, NULL, NULL);
  41. }
  42. static void onDtmfDigit(pjsua_call_id callId, int digit) {
  43. pjLogger.notice("DTMF digit '%c' (call %d).", digit, callId);
  44. }
  45. static void onCallState(pjsua_call_id call_id,
  46. pjsip_event *e) {
  47. pjsua_call_info ci;
  48. PJ_UNUSED_ARG(e);
  49. pjsua_call_get_info(call_id, &ci);
  50. sip::PjsuaCommunicator *communicator
  51. = reinterpret_cast<sip::PjsuaCommunicator *>(pjsua_call_get_user_data(call_id));
  52. pjLogger.info("Call %d state=%s.", call_id, ci.state_text.ptr);
  53. string address = string(ci.remote_info.ptr);
  54. boost::replace_all(address, "<", "");
  55. boost::replace_all(address, ">", "");
  56. if (ci.state == PJSIP_INV_STATE_CONFIRMED) {
  57. auto msgText = "Start call from " + address + ".";
  58. pjLogger.notice(msgText);
  59. communicator->onStateChange(msgText);
  60. } else if (ci.state == PJSIP_INV_STATE_DISCONNECTED) {
  61. auto msgText = "End call from " + address + ".";
  62. pjLogger.notice(msgText);
  63. communicator->onStateChange(msgText);
  64. }
  65. }
  66. static void pjLogToLog4CppBridgeFunction(int level, const char *data, int len) {
  67. using namespace log4cpp;
  68. std::map<int, Priority::Value> prioritiesMap = {
  69. {1, Priority::ERROR},
  70. {2, Priority::WARN},
  71. {3, Priority::NOTICE},
  72. {4, Priority::INFO},
  73. {5, Priority::DEBUG},
  74. {6, Priority::DEBUG}
  75. };
  76. string message(data);
  77. message = message.substr(0, message.size() - 1); // remove newline
  78. pjLogger << prioritiesMap.at(level) << message;
  79. }
  80. sip::PjsuaCommunicator::PjsuaCommunicator()
  81. : logger(log4cpp::Category::getInstance("SipCommunicator")),
  82. callbackLogger(log4cpp::Category::getInstance("SipCommunicatorCallback")) {
  83. pj_status_t status;
  84. if (pjsuaCommunicator != nullptr) {
  85. throw sip::Exception("this is a singleton class");
  86. }
  87. pjsuaCommunicator = this;
  88. status = pjsua_create();
  89. if (status != PJ_SUCCESS) {
  90. throw sip::Exception("Error in pjsua_create()", status);
  91. }
  92. pj_log_set_log_func(pjLogToLog4CppBridgeFunction);
  93. pjsua_config generalConfig;
  94. pjsua_config_default(&generalConfig);
  95. string userAgent = "Mumsi Mumble-SIP Bridge";
  96. generalConfig.user_agent = toPjString(userAgent);
  97. generalConfig.max_calls = 1;
  98. generalConfig.cb.on_incoming_call = &onIncomingCall;
  99. generalConfig.cb.on_dtmf_digit = &onDtmfDigit;
  100. generalConfig.cb.on_call_media_state = &PjsuaCommunicator_onCallMediaState;
  101. generalConfig.cb.on_call_state = &onCallState;
  102. pjsua_logging_config logConfig;
  103. pjsua_logging_config_default(&logConfig);
  104. logConfig.cb = pjLogToLog4CppBridgeFunction;
  105. logConfig.console_level = 5;
  106. pjsua_media_config mediaConfig;
  107. pjsua_media_config_default(&mediaConfig);
  108. mediaConfig.max_media_ports = 3;
  109. status = pjsua_init(&generalConfig, &logConfig, &mediaConfig);
  110. if (status != PJ_SUCCESS) {
  111. throw sip::Exception("error in pjsua_init", status);
  112. }
  113. status = pjsua_set_null_snd_dev();
  114. if (status != PJ_SUCCESS) {
  115. throw sip::Exception("error setting null device", status);
  116. }
  117. pj_caching_pool cachingPool;
  118. pj_caching_pool_init(&cachingPool, &pj_pool_factory_default_policy, 0);
  119. pool = pj_pool_create(&cachingPool.factory, "wav", 32768, 8192, nullptr);
  120. // todo calculate sizes
  121. pjmedia_circ_buf_create(pool, 960 * 10, &inputBuff);
  122. mediaPort = createMediaPort();
  123. }
  124. void sip::PjsuaCommunicator::connect(
  125. std::string host,
  126. std::string user,
  127. std::string password,
  128. unsigned int port) {
  129. pj_status_t status;
  130. pjsua_transport_config transportConfig;
  131. pjsua_transport_config_default(&transportConfig);
  132. transportConfig.port = port;
  133. status = pjsua_transport_create(PJSIP_TRANSPORT_UDP, &transportConfig, NULL);
  134. if (status != PJ_SUCCESS) {
  135. throw sip::Exception("Error creating transport", status);
  136. }
  137. status = pjsua_start();
  138. if (status != PJ_SUCCESS) {
  139. throw sip::Exception("Error starting sip", status);
  140. }
  141. registerAccount(host, user, password);
  142. }
  143. sip::PjsuaCommunicator::~PjsuaCommunicator() {
  144. pjsua_destroy();
  145. if (mediaPort != nullptr) {
  146. delete mediaPort;
  147. }
  148. }
  149. pjmedia_port *sip::PjsuaCommunicator::createMediaPort() {
  150. pjmedia_port *mp = new pjmedia_port();
  151. string name = "PjsuaMP";
  152. auto pjName = toPjString(name);
  153. pj_status_t status = pjmedia_port_info_init(&(mp->info),
  154. &pjName,
  155. PJMEDIA_SIG_CLASS_PORT_AUD('s', 'i'),
  156. SAMPLING_RATE,
  157. 1,
  158. 16,
  159. SAMPLING_RATE * 20 /
  160. 1000); // todo recalculate to match mumble specs
  161. if (status != PJ_SUCCESS) {
  162. throw sip::Exception("error while calling pjmedia_port_info_init().", status);
  163. }
  164. mp->get_frame = &MediaPort_getFrameRawCallback;
  165. mp->put_frame = &MediaPort_putFrameRawCallback;
  166. mp->port_data.pdata = this;
  167. return mp;
  168. }
  169. pj_status_t sip::MediaPort_getFrameRawCallback(pjmedia_port *port,
  170. pjmedia_frame *frame) {
  171. PjsuaCommunicator *communicator = static_cast<PjsuaCommunicator *>(port->port_data.pdata);
  172. frame->type = PJMEDIA_FRAME_TYPE_AUDIO;
  173. return communicator->mediaPortGetFrame(frame);
  174. }
  175. pj_status_t sip::MediaPort_putFrameRawCallback(pjmedia_port *port,
  176. pjmedia_frame *frame) {
  177. PjsuaCommunicator *communicator = static_cast<PjsuaCommunicator *>(port->port_data.pdata);
  178. pj_int16_t *samples = static_cast<pj_int16_t *>(frame->buf);
  179. pj_size_t count = frame->size / 2 / PJMEDIA_PIA_CCNT(&port->info);
  180. frame->type = PJMEDIA_FRAME_TYPE_AUDIO;
  181. communicator->mediaPortPutFrame(samples, count);
  182. return PJ_SUCCESS;
  183. }
  184. pj_status_t sip::PjsuaCommunicator::mediaPortGetFrame(pjmedia_frame *frame) {
  185. std::unique_lock<std::mutex> lock(inBuffAccessMutex);
  186. pj_int16_t *samples = static_cast<pj_int16_t *>(frame->buf);
  187. pj_size_t count = frame->size / 2 / PJMEDIA_PIA_CCNT(&mediaPort->info);
  188. pj_size_t availableSamples = pjmedia_circ_buf_get_len(inputBuff);
  189. const int samplesToRead = std::min(count, availableSamples);
  190. callbackLogger.debug("Pulling %d samples from in-buff.", samplesToRead);
  191. pjmedia_circ_buf_read(inputBuff, samples, samplesToRead);
  192. if (availableSamples < count) {
  193. callbackLogger.debug("Requested %d samples, available %d, filling remaining with zeros.", count,
  194. availableSamples);
  195. for (int i = samplesToRead; i < count; ++i) {
  196. samples[i] = 0;
  197. }
  198. }
  199. return PJ_SUCCESS;
  200. }
  201. void sip::PjsuaCommunicator::mediaPortPutFrame(pj_int16_t *samples, pj_size_t count) {
  202. if (count > 0) {
  203. callbackLogger.debug("Calling onIncomingPcmSamples with %d samples.", count);
  204. onIncomingPcmSamples(samples, count);
  205. }
  206. }
  207. void sip::PjsuaCommunicator::registerAccount(string host, string user, string password) {
  208. pjsua_acc_config accConfig;
  209. pjsua_acc_config_default(&accConfig);
  210. string uri = string("sip:") + user + "@" + host;
  211. string regUri = "sip:" + host;
  212. string scheme = "digest";
  213. pj_status_t status;
  214. status = pjsua_verify_sip_url(uri.c_str());
  215. if (status != PJ_SUCCESS) {
  216. throw sip::Exception("invalid URI format", status);
  217. }
  218. logger.info("Registering account for URI: %s.", uri.c_str());
  219. accConfig.id = toPjString(uri);
  220. accConfig.reg_uri = toPjString(regUri);
  221. accConfig.cred_count = 1;
  222. accConfig.cred_info[0].realm = toPjString(host);
  223. accConfig.cred_info[0].scheme = toPjString(scheme);
  224. accConfig.cred_info[0].username = toPjString(user);
  225. accConfig.cred_info[0].data_type = PJSIP_CRED_DATA_PLAIN_PASSWD;
  226. accConfig.cred_info[0].data = toPjString(password);
  227. pjsua_acc_id acc_id;
  228. status = pjsua_acc_add(&accConfig, PJ_TRUE, &acc_id);
  229. if (status != PJ_SUCCESS) {
  230. throw sip::Exception("failed to register account", status);
  231. }
  232. }
  233. void sip::PjsuaCommunicator::sendPcmSamples(int16_t *samples, unsigned int length) {
  234. std::unique_lock<std::mutex> lock(inBuffAccessMutex);
  235. callbackLogger.debug("Pushing %d samples to in-buff.", length);
  236. pjmedia_circ_buf_write(inputBuff, samples, length);
  237. }