/plugins/janus_videoroom.c

https://github.com/michaelsharpe/janus-gateway · C · 1474 lines · 1272 code · 55 blank · 147 comment · 351 complexity · 0144ce9361b8e0bef8e99b0a114685bf MD5 · raw file

  1. /*! \file janus_videoroom.c
  2. * \author Lorenzo Miniero <lorenzo@meetecho.com>
  3. * \copyright GNU Affero General Public License v3
  4. * \brief Janus VideoRoom plugin
  5. * \details This is a plugin implementing a videoconferencing MCU for Janus.
  6. * This means that the plugin implements a virtual conferencing room peers
  7. * can join and leave at any time. This room is based on a Publish/Subscribe
  8. * pattern. Each peer can publish his/her own live audio/video feeds: this
  9. * feed becomes an available stream in the room the other participants can
  10. * attach to. This means that this plugin allows the realization of several
  11. * different scenarios, ranging from a simple webinar (one speaker, several
  12. * listeners) to a fully meshed video conference (each peer sending and
  13. * receiving to and from all the others).
  14. *
  15. * Considering that this plugin allows for several different WebRTC PeerConnections
  16. * to be on at the same time for the same peer (specifically, each peer
  17. * potentially has 1 PeerConnection on for publishing and N on for subscriptions
  18. * from other peers), each peer may need to attach several times to the same
  19. * plugin for every stream: this means that each peer needs to have at least one
  20. * handle active for managing its relation with the plugin (joining a room,
  21. * leaving a room, muting/unmuting, publishing, receiving events), and needs
  22. * to open a new one each time he/she wants to subscribe to a feed from
  23. * another participant. The handle used for a subscription, however, would
  24. * be logically a "slave" to the master one used for managing the room: this
  25. * means that it cannot be used, for instance, to unmute in the room, as its
  26. * only purpose would be to provide a context in which creating the sendonly
  27. * PeerConnection for the subscription to the active participant.
  28. *
  29. * Rooms to make available are listed in the plugin configuration file.
  30. * A pre-filled configuration file is provided in \c conf/janus.plugin.videoroom.cfg
  31. * and includes a demo room for testing. The same plugin is also used
  32. * dynamically (that is, with rooms created on the fly via API) in the
  33. * Screen Sharing demo as well.
  34. *
  35. * To add more rooms or modify the existing one, you can use the following
  36. * syntax:
  37. *
  38. * \verbatim
  39. [<unique room ID>]
  40. description = This is my awesome room
  41. publishers = <max number of concurrent senders> (e.g., 6 for a video
  42. conference or 1 for a webinar)
  43. bitrate = <max video bitrate for senders> (e.g., 128000)
  44. fir_freq = <send a FIR to publishers every fir_freq seconds> (0=disable)
  45. \endverbatim
  46. *
  47. * \ingroup plugins
  48. * \ref plugins
  49. */
  50. #include "plugin.h"
  51. #include <jansson.h>
  52. #include "../apierror.h"
  53. #include "../config.h"
  54. #include "../mutex.h"
  55. #include "../rtcp.h"
  56. #include "../utils.h"
  57. /* Plugin information */
  58. #define JANUS_VIDEOROOM_VERSION 2
  59. #define JANUS_VIDEOROOM_VERSION_STRING "0.0.2"
  60. #define JANUS_VIDEOROOM_DESCRIPTION "This is a plugin implementing a videoconferencing MCU for Janus, something like Licode."
  61. #define JANUS_VIDEOROOM_NAME "JANUS VideoRoom plugin"
  62. #define JANUS_VIDEOROOM_AUTHOR "Meetecho s.r.l."
  63. #define JANUS_VIDEOROOM_PACKAGE "janus.plugin.videoroom"
  64. /* Plugin methods */
  65. janus_plugin *create(void);
  66. int janus_videoroom_init(janus_callbacks *callback, const char *config_path);
  67. void janus_videoroom_destroy(void);
  68. int janus_videoroom_get_version(void);
  69. const char *janus_videoroom_get_version_string(void);
  70. const char *janus_videoroom_get_description(void);
  71. const char *janus_videoroom_get_name(void);
  72. const char *janus_videoroom_get_author(void);
  73. const char *janus_videoroom_get_package(void);
  74. void janus_videoroom_create_session(janus_plugin_session *handle, int *error);
  75. void janus_videoroom_handle_message(janus_plugin_session *handle, char *transaction, char *message, char *sdp_type, char *sdp);
  76. void janus_videoroom_setup_media(janus_plugin_session *handle);
  77. void janus_videoroom_incoming_rtp(janus_plugin_session *handle, int video, char *buf, int len);
  78. void janus_videoroom_incoming_rtcp(janus_plugin_session *handle, int video, char *buf, int len);
  79. void janus_videoroom_hangup_media(janus_plugin_session *handle);
  80. void janus_videoroom_destroy_session(janus_plugin_session *handle, int *error);
  81. /* Plugin setup */
  82. static janus_plugin janus_videoroom_plugin =
  83. {
  84. .init = janus_videoroom_init,
  85. .destroy = janus_videoroom_destroy,
  86. .get_version = janus_videoroom_get_version,
  87. .get_version_string = janus_videoroom_get_version_string,
  88. .get_description = janus_videoroom_get_description,
  89. .get_name = janus_videoroom_get_name,
  90. .get_author = janus_videoroom_get_author,
  91. .get_package = janus_videoroom_get_package,
  92. .create_session = janus_videoroom_create_session,
  93. .handle_message = janus_videoroom_handle_message,
  94. .setup_media = janus_videoroom_setup_media,
  95. .incoming_rtp = janus_videoroom_incoming_rtp,
  96. .incoming_rtcp = janus_videoroom_incoming_rtcp,
  97. .hangup_media = janus_videoroom_hangup_media,
  98. .destroy_session = janus_videoroom_destroy_session,
  99. };
  100. /* Plugin creator */
  101. janus_plugin *create(void) {
  102. JANUS_LOG(LOG_VERB, "%s created!\n", JANUS_VIDEOROOM_NAME);
  103. return &janus_videoroom_plugin;
  104. }
  105. /* Useful stuff */
  106. static int initialized = 0, stopping = 0;
  107. static janus_callbacks *gateway = NULL;
  108. static GThread *handler_thread;
  109. static void *janus_videoroom_handler(void *data);
  110. static void janus_videoroom_relay_rtp_packet(gpointer data, gpointer user_data);
  111. char *string_replace(char *message, char *old, char *new, int *modified);
  112. typedef enum janus_videoroom_p_type {
  113. janus_videoroom_p_type_none = 0,
  114. janus_videoroom_p_type_subscriber,
  115. janus_videoroom_p_type_publisher,
  116. } janus_videoroom_p_type;
  117. typedef struct janus_videoroom_message {
  118. janus_plugin_session *handle;
  119. char *transaction;
  120. char *message;
  121. char *sdp_type;
  122. char *sdp;
  123. } janus_videoroom_message;
  124. GQueue *messages;
  125. void janus_videoroom_message_free(janus_videoroom_message *msg);
  126. void janus_videoroom_message_free(janus_videoroom_message *msg) {
  127. if(!msg)
  128. return;
  129. msg->handle = NULL;
  130. if(msg->transaction != NULL)
  131. g_free(msg->transaction);
  132. msg->transaction = NULL;
  133. if(msg->message != NULL)
  134. g_free(msg->message);
  135. msg->message = NULL;
  136. if(msg->sdp_type != NULL)
  137. g_free(msg->sdp_type);
  138. msg->sdp_type = NULL;
  139. if(msg->sdp != NULL)
  140. g_free(msg->sdp);
  141. msg->sdp = NULL;
  142. g_free(msg);
  143. msg = NULL;
  144. }
  145. typedef struct janus_videoroom {
  146. guint64 room_id; /* Unique room ID */
  147. gchar *room_name; /* Room description */
  148. int max_publishers; /* Maximum number of concurrent publishers */
  149. uint64_t bitrate; /* Global bitrate limit */
  150. uint16_t fir_freq; /* Regular FIR frequency (0=disabled) */
  151. gboolean destroy;
  152. GHashTable *participants; /* Map of potential publishers (we get listeners from them) */
  153. } janus_videoroom;
  154. GHashTable *rooms;
  155. janus_mutex rooms_mutex;
  156. typedef struct janus_videoroom_session {
  157. janus_plugin_session *handle;
  158. janus_videoroom_p_type participant_type;
  159. gpointer participant;
  160. gboolean started;
  161. gboolean stopping;
  162. gboolean destroy;
  163. } janus_videoroom_session;
  164. GHashTable *sessions;
  165. janus_mutex sessions_mutex;
  166. typedef struct janus_videoroom_participant {
  167. janus_videoroom_session *session;
  168. janus_videoroom *room; /* Room */
  169. guint64 user_id; /* Unique ID in the room */
  170. gchar *display; /* Display name (just for fun) */
  171. gchar *sdp; /* The SDP this publisher negotiated, if any */
  172. gboolean audio_active;
  173. gboolean video_active;
  174. gboolean firefox; /* We send Firefox users a different kind of FIR */
  175. uint64_t bitrate;
  176. gint64 fir_latest; /* Time of latest sent FIR (to avoid flooding) */
  177. gint fir_seq; /* FIR sequence number */
  178. GSList *listeners;
  179. janus_mutex listeners_mutex;
  180. } janus_videoroom_participant;
  181. typedef struct janus_videoroom_listener {
  182. janus_videoroom_session *session;
  183. janus_videoroom *room; /* Room */
  184. janus_videoroom_participant *feed; /* Participant this listener is subscribed to */
  185. gboolean paused;
  186. } janus_videoroom_listener;
  187. typedef struct janus_videoroom_rtp_relay_packet {
  188. char *data;
  189. gint length;
  190. gint is_video;
  191. } janus_videoroom_rtp_relay_packet;
  192. /* SDP offer/answer templates */
  193. static const char *sdp_template =
  194. "v=0\r\n"
  195. "o=- %"SCNu64" %"SCNu64" IN IP4 127.0.0.1\r\n" /* We need current time here */
  196. "s=%s\r\n" /* Video room name */
  197. "t=0 0\r\n"
  198. "%s%s"; /* Audio and/or video m-lines */
  199. static const char *sdp_a_template =
  200. "m=audio 1 RTP/SAVPF %d\r\n" /* Opus payload type */
  201. "c=IN IP4 1.1.1.1\r\n"
  202. "a=%s\r\n" /* Media direction */
  203. "a=rtpmap:%d opus/48000/2\r\n"; /* Opus payload type */
  204. static const char *sdp_v_template =
  205. "m=video 1 RTP/SAVPF %d\r\n" /* VP8 payload type */
  206. "c=IN IP4 1.1.1.1\r\n"
  207. "b=AS:%d\r\n" /* Bandwidth */
  208. "a=%s\r\n" /* Media direction */
  209. "a=rtpmap:%d VP8/90000\r\n" /* VP8 payload type */
  210. "a=rtcp-fb:%d ccm fir\r\n" /* VP8 payload type */
  211. "a=rtcp-fb:%d nack\r\n" /* VP8 payload type */
  212. "a=rtcp-fb:%d nack pli\r\n" /* VP8 payload type */
  213. "a=rtcp-fb:%d goog-remb\r\n"; /* VP8 payload type */
  214. /* Error codes */
  215. #define JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR 499
  216. #define JANUS_VIDEOROOM_ERROR_NO_MESSAGE 421
  217. #define JANUS_VIDEOROOM_ERROR_INVALID_JSON 422
  218. #define JANUS_VIDEOROOM_ERROR_INVALID_REQUEST 423
  219. #define JANUS_VIDEOROOM_ERROR_JOIN_FIRST 424
  220. #define JANUS_VIDEOROOM_ERROR_ALREADY_JOINED 425
  221. #define JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM 426
  222. #define JANUS_VIDEOROOM_ERROR_NO_SUCH_FEED 427
  223. #define JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT 428
  224. #define JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT 429
  225. #define JANUS_VIDEOROOM_ERROR_INVALID_SDP_TYPE 430
  226. #define JANUS_VIDEOROOM_ERROR_PUBLISHERS_FULL 431
  227. /* Plugin implementation */
  228. int janus_videoroom_init(janus_callbacks *callback, const char *config_path) {
  229. if(stopping) {
  230. /* Still stopping from before */
  231. return -1;
  232. }
  233. if(callback == NULL || config_path == NULL) {
  234. /* Invalid arguments */
  235. return -1;
  236. }
  237. /* Read configuration */
  238. char filename[255];
  239. sprintf(filename, "%s/%s.cfg", config_path, JANUS_VIDEOROOM_PACKAGE);
  240. JANUS_LOG(LOG_VERB, "Configuration file: %s\n", filename);
  241. janus_config *config = janus_config_parse(filename);
  242. if(config != NULL)
  243. janus_config_print(config);
  244. rooms = g_hash_table_new(NULL, NULL);
  245. janus_mutex_init(&rooms_mutex);
  246. sessions = g_hash_table_new(NULL, NULL);
  247. janus_mutex_init(&sessions_mutex);
  248. messages = g_queue_new();
  249. /* This is the callback we'll need to invoke to contact the gateway */
  250. gateway = callback;
  251. /* Parse configuration to populate the rooms list */
  252. if(config != NULL) {
  253. janus_config_category *cat = janus_config_get_categories(config);
  254. while(cat != NULL) {
  255. if(cat->name == NULL) {
  256. cat = cat->next;
  257. continue;
  258. }
  259. JANUS_LOG(LOG_VERB, "Adding video room '%s'\n", cat->name);
  260. janus_config_item *desc = janus_config_get_item(cat, "description");
  261. janus_config_item *bitrate = janus_config_get_item(cat, "bitrate");
  262. janus_config_item *maxp = janus_config_get_item(cat, "publishers");
  263. janus_config_item *firfreq = janus_config_get_item(cat, "fir_freq");
  264. /* Create the video mcu room */
  265. janus_videoroom *videoroom = calloc(1, sizeof(janus_videoroom));
  266. if(videoroom == NULL) {
  267. JANUS_LOG(LOG_FATAL, "Memory error!\n");
  268. continue;
  269. }
  270. videoroom->room_id = atoi(cat->name);
  271. char *description = NULL;
  272. if(desc != NULL && desc->value != NULL)
  273. description = g_strdup(desc->value);
  274. else
  275. description = g_strdup(cat->name);
  276. if(description == NULL) {
  277. JANUS_LOG(LOG_FATAL, "Memory error!\n");
  278. continue;
  279. }
  280. videoroom->room_name = description;
  281. videoroom->max_publishers = 3; /* FIXME How should we choose a default? */
  282. if(maxp != NULL && maxp->value != NULL)
  283. videoroom->max_publishers = atol(maxp->value);
  284. if(videoroom->max_publishers < 0)
  285. videoroom->max_publishers = 3; /* FIXME How should we choose a default? */
  286. videoroom->bitrate = 0;
  287. if(bitrate != NULL && bitrate->value != NULL)
  288. videoroom->bitrate = atol(bitrate->value);
  289. if(videoroom->bitrate > 0 && videoroom->bitrate < 64000)
  290. videoroom->bitrate = 64000; /* Don't go below 64k */
  291. videoroom->fir_freq = 0;
  292. if(firfreq != NULL && firfreq->value != NULL)
  293. videoroom->fir_freq = atol(firfreq->value);
  294. videoroom->destroy = 0;
  295. videoroom->participants = g_hash_table_new(NULL, NULL);
  296. janus_mutex_lock(&rooms_mutex);
  297. g_hash_table_insert(rooms, GUINT_TO_POINTER(videoroom->room_id), videoroom);
  298. janus_mutex_unlock(&rooms_mutex);
  299. JANUS_LOG(LOG_VERB, "Created videoroom: %"SCNu64" (%s)\n", videoroom->room_id, videoroom->room_name);
  300. cat = cat->next;
  301. }
  302. /* Done */
  303. janus_config_destroy(config);
  304. config = NULL;
  305. }
  306. /* Show available rooms */
  307. janus_mutex_lock(&rooms_mutex);
  308. GList *rooms_list = g_hash_table_get_values(rooms);
  309. GList *r = rooms_list;
  310. while(r) {
  311. janus_videoroom *vr = (janus_videoroom *)r->data;
  312. JANUS_LOG(LOG_VERB, " ::: [%"SCNu64"][%s] %"SCNu64", max %d publishers, FIR frequency of %d seconds\n", vr->room_id, vr->room_name, vr->bitrate, vr->max_publishers, vr->fir_freq);
  313. r = r->next;
  314. }
  315. g_list_free(rooms_list);
  316. janus_mutex_unlock(&rooms_mutex);
  317. initialized = 1;
  318. /* Launch the thread that will handle incoming messages */
  319. GError *error = NULL;
  320. handler_thread = g_thread_try_new("janus videoroom handler", janus_videoroom_handler, NULL, &error);
  321. if(error != NULL) {
  322. initialized = 0;
  323. /* Something went wrong... */
  324. JANUS_LOG(LOG_ERR, "Got error %d (%s) trying to launch thread...\n", error->code, error->message ? error->message : "??");
  325. return -1;
  326. }
  327. JANUS_LOG(LOG_INFO, "%s initialized!\n", JANUS_VIDEOROOM_NAME);
  328. return 0;
  329. }
  330. void janus_videoroom_destroy() {
  331. if(!initialized)
  332. return;
  333. stopping = 1;
  334. if(handler_thread != NULL) {
  335. g_thread_join(handler_thread);
  336. }
  337. handler_thread = NULL;
  338. /* FIXME We should destroy the sessions cleanly */
  339. janus_mutex_lock(&sessions_mutex);
  340. g_hash_table_destroy(sessions);
  341. janus_mutex_unlock(&sessions_mutex);
  342. janus_mutex_lock(&rooms_mutex);
  343. g_hash_table_destroy(rooms);
  344. janus_mutex_unlock(&rooms_mutex);
  345. g_queue_free(messages);
  346. rooms = NULL;
  347. initialized = 0;
  348. stopping = 0;
  349. JANUS_LOG(LOG_INFO, "%s destroyed!\n", JANUS_VIDEOROOM_NAME);
  350. }
  351. int janus_videoroom_get_version() {
  352. return JANUS_VIDEOROOM_VERSION;
  353. }
  354. const char *janus_videoroom_get_version_string() {
  355. return JANUS_VIDEOROOM_VERSION_STRING;
  356. }
  357. const char *janus_videoroom_get_description() {
  358. return JANUS_VIDEOROOM_DESCRIPTION;
  359. }
  360. const char *janus_videoroom_get_name() {
  361. return JANUS_VIDEOROOM_NAME;
  362. }
  363. const char *janus_videoroom_get_author() {
  364. return JANUS_VIDEOROOM_AUTHOR;
  365. }
  366. const char *janus_videoroom_get_package() {
  367. return JANUS_VIDEOROOM_PACKAGE;
  368. }
  369. void janus_videoroom_create_session(janus_plugin_session *handle, int *error) {
  370. if(stopping || !initialized) {
  371. *error = -1;
  372. return;
  373. }
  374. janus_videoroom_session *session = (janus_videoroom_session *)calloc(1, sizeof(janus_videoroom_session));
  375. if(session == NULL) {
  376. JANUS_LOG(LOG_FATAL, "Memory error!\n");
  377. *error = -2;
  378. return;
  379. }
  380. session->handle = handle;
  381. session->participant_type = janus_videoroom_p_type_none;
  382. session->participant = NULL;
  383. handle->plugin_handle = session;
  384. janus_mutex_lock(&sessions_mutex);
  385. g_hash_table_insert(sessions, handle, session);
  386. janus_mutex_unlock(&sessions_mutex);
  387. return;
  388. }
  389. void janus_videoroom_destroy_session(janus_plugin_session *handle, int *error) {
  390. if(stopping || !initialized) {
  391. *error = -1;
  392. return;
  393. }
  394. janus_videoroom_session *session = (janus_videoroom_session *)handle->plugin_handle;
  395. if(!session) {
  396. JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
  397. *error = -2;
  398. return;
  399. }
  400. if(session->destroy) {
  401. JANUS_LOG(LOG_WARN, "Session already destroyed...\n");
  402. return;
  403. }
  404. JANUS_LOG(LOG_VERB, "Removing Video Room session...\n");
  405. janus_mutex_lock(&sessions_mutex);
  406. g_hash_table_remove(sessions, handle);
  407. janus_mutex_unlock(&sessions_mutex);
  408. if(session->participant_type == janus_videoroom_p_type_publisher) {
  409. /* TODO Get rid of this publisher and its listeners */
  410. } else if(session->participant_type == janus_videoroom_p_type_subscriber) {
  411. /* TODO Detach this listener from its subscriber */
  412. }
  413. janus_videoroom_hangup_media(handle);
  414. /* Cleaning up and removing the session is done in a lazy way */
  415. session->destroy = TRUE;
  416. return;
  417. }
  418. void janus_videoroom_handle_message(janus_plugin_session *handle, char *transaction, char *message, char *sdp_type, char *sdp) {
  419. if(stopping || !initialized)
  420. return;
  421. JANUS_LOG(LOG_VERB, "%s\n", message);
  422. janus_videoroom_message *msg = calloc(1, sizeof(janus_videoroom_message));
  423. if(msg == NULL) {
  424. JANUS_LOG(LOG_FATAL, "Memory error!\n");
  425. return;
  426. }
  427. msg->handle = handle;
  428. msg->transaction = transaction;
  429. msg->message = message;
  430. msg->sdp_type = sdp_type;
  431. msg->sdp = sdp;
  432. g_queue_push_tail(messages, msg);
  433. }
  434. void janus_videoroom_setup_media(janus_plugin_session *handle) {
  435. JANUS_LOG(LOG_INFO, "WebRTC media is now available\n");
  436. if(stopping || !initialized)
  437. return;
  438. janus_videoroom_session *session = (janus_videoroom_session *)handle->plugin_handle;
  439. if(!session) {
  440. JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
  441. return;
  442. }
  443. if(session->destroy)
  444. return;
  445. /* Media relaying can start now */
  446. session->started = TRUE;
  447. /* If this is a listener, ask the publisher a FIR */
  448. if(session->participant && session->participant_type == janus_videoroom_p_type_subscriber) {
  449. janus_videoroom_listener *l = (janus_videoroom_listener *)session->participant;
  450. if(l && l->feed) {
  451. janus_videoroom_participant *p = l->feed;
  452. if(p && p->session) {
  453. /* Send a FIR */
  454. char buf[20];
  455. memset(buf, 0, 20);
  456. if(!p->firefox)
  457. janus_rtcp_fir((char *)&buf, 20, &p->fir_seq);
  458. else
  459. janus_rtcp_fir_legacy((char *)&buf, 20, &p->fir_seq);
  460. JANUS_LOG(LOG_VERB, "New listener available, sending FIR to %s\n", p->display);
  461. gateway->relay_rtcp(p->session->handle, 1, buf, 20);
  462. /* Send a PLI too, just in case... */
  463. memset(buf, 0, 12);
  464. janus_rtcp_pli((char *)&buf, 12);
  465. JANUS_LOG(LOG_VERB, "New listener available, sending PLI to %s\n", p->display);
  466. gateway->relay_rtcp(p->session->handle, 1, buf, 12);
  467. }
  468. }
  469. }
  470. }
  471. void janus_videoroom_incoming_rtp(janus_plugin_session *handle, int video, char *buf, int len) {
  472. if(handle == NULL || handle->stopped || stopping || !initialized || !gateway)
  473. return;
  474. janus_videoroom_session *session = (janus_videoroom_session *)handle->plugin_handle;
  475. if(!session || session->destroy || !session->participant || session->participant_type != janus_videoroom_p_type_publisher)
  476. return;
  477. janus_videoroom_participant *participant = (janus_videoroom_participant *)session->participant;
  478. if((!video && participant->audio_active) || (video && participant->video_active)) {
  479. janus_videoroom_rtp_relay_packet packet;
  480. packet.data = buf;
  481. packet.length = len;
  482. packet.is_video = video;
  483. g_slist_foreach(participant->listeners, janus_videoroom_relay_rtp_packet, &packet);
  484. if(video && participant->video_active && (participant->room->fir_freq > 0)) {
  485. /* FIXME Very ugly hack to generate RTCP every tot seconds/frames */
  486. gint64 now = janus_get_monotonic_time();
  487. if((now-participant->fir_latest) >= (participant->room->fir_freq*G_USEC_PER_SEC)) {
  488. /* FIXME We send a FIR every tot seconds */
  489. participant->fir_latest = now;
  490. char buf[20];
  491. memset(buf, 0, 20);
  492. janus_rtcp_fir((char *)&buf, 20, &participant->fir_seq);
  493. //~ if(!participant->firefox)
  494. //~ janus_rtcp_fir((char *)&buf, 20, &participant->fir_seq);
  495. //~ else
  496. //~ janus_rtcp_fir_legacy((char *)&buf, 20, &participant->fir_seq);
  497. JANUS_LOG(LOG_VERB, "Sending FIR to %s\n", participant->display);
  498. gateway->relay_rtcp(handle, video, buf, 20);
  499. /* Send a PLI too, just in case... */
  500. memset(buf, 0, 12);
  501. janus_rtcp_pli((char *)&buf, 12);
  502. JANUS_LOG(LOG_VERB, "Sending PLI to %s\n", participant->display);
  503. gateway->relay_rtcp(handle, video, buf, 12);
  504. if(participant->firefox && participant->bitrate > 0) {
  505. /* Now that we're there, let's send a REMB as well */
  506. janus_rtcp_remb((char *)&buf, 24, participant->bitrate);
  507. gateway->relay_rtcp(handle, video, buf, 24);
  508. }
  509. }
  510. }
  511. }
  512. }
  513. void janus_videoroom_incoming_rtcp(janus_plugin_session *handle, int video, char *buf, int len) {
  514. if(handle == NULL || handle->stopped || stopping || !initialized || !gateway)
  515. return;
  516. janus_videoroom_session *session = (janus_videoroom_session *)handle->plugin_handle;
  517. if(!session || session->destroy || !session->participant || !video)
  518. return;
  519. if(session->participant_type == janus_videoroom_p_type_subscriber) {
  520. /* FIXME Badly: we're blinding forwarding the listener RTCP to the publisher: this probably means confusing him... */
  521. janus_videoroom_listener *l = (janus_videoroom_listener *)session->participant;
  522. if(l && l->feed) {
  523. janus_videoroom_participant *p = l->feed;
  524. if(p && p->session) {
  525. if((!video && p->audio_active) || (video && p->video_active)) {
  526. if(p->bitrate > 0)
  527. janus_rtcp_cap_remb(buf, len, p->bitrate);
  528. gateway->relay_rtcp(p->session->handle, video, buf, len);
  529. }
  530. }
  531. }
  532. } else if(session->participant_type == janus_videoroom_p_type_publisher) {
  533. /* FIXME Badly: we're just bouncing the incoming RTCP back with modified REMB, we need to improve this... */
  534. janus_videoroom_participant *participant = (janus_videoroom_participant *)session->participant;
  535. if(participant && participant->session) {
  536. if((!video && participant->audio_active) || (video && participant->video_active)) {
  537. if(participant->bitrate > 0)
  538. janus_rtcp_cap_remb(buf, len, participant->bitrate);
  539. gateway->relay_rtcp(handle, video, buf, len);
  540. /* FIXME Badly: we're also blinding forwarding the publisher RTCP to all the listeners: this probably means confusing them... */
  541. if(participant->listeners != NULL) {
  542. GSList *ps = participant->listeners;
  543. while(ps) {
  544. janus_videoroom_listener *l = (janus_videoroom_listener *)ps->data;
  545. if(l->session && l->session->handle) {
  546. gateway->relay_rtcp(l->session->handle, video, buf, len);
  547. }
  548. ps = ps->next;
  549. }
  550. }
  551. }
  552. }
  553. }
  554. }
  555. void janus_videoroom_hangup_media(janus_plugin_session *handle) {
  556. JANUS_LOG(LOG_INFO, "No WebRTC media anymore\n");
  557. if(stopping || !initialized)
  558. return;
  559. janus_videoroom_session *session = (janus_videoroom_session *)handle->plugin_handle;
  560. if(!session) {
  561. JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
  562. return;
  563. }
  564. if(session->destroy)
  565. return;
  566. /* Send an event to the browser and tell it's over */
  567. if(session->participant_type == janus_videoroom_p_type_publisher) {
  568. /* Get rid of publisher */
  569. janus_videoroom_participant *participant = (janus_videoroom_participant *)session->participant;
  570. participant->audio_active = FALSE;
  571. participant->video_active = FALSE;
  572. json_t *event = json_object();
  573. json_object_set_new(event, "videoroom", json_string("event"));
  574. json_object_set_new(event, "room", json_integer(participant->room->room_id));
  575. json_object_set_new(event, "leaving", json_integer(participant->user_id));
  576. char *leaving_text = json_dumps(event, JSON_INDENT(3));
  577. json_decref(event);
  578. g_hash_table_remove(participant->room->participants, GUINT_TO_POINTER(participant->user_id));
  579. GList *participants_list = g_hash_table_get_values(participant->room->participants);
  580. GList *ps = participants_list;
  581. while(ps) {
  582. janus_videoroom_participant *p = (janus_videoroom_participant *)ps->data;
  583. if(p == participant) {
  584. ps = ps->next;
  585. continue; /* Skip the leaving publisher itself */
  586. }
  587. JANUS_LOG(LOG_VERB, "Notifying participant %"SCNu64" (%s)\n", p->user_id, p->display);
  588. int ret = gateway->push_event(p->session->handle, &janus_videoroom_plugin, NULL, leaving_text, NULL, NULL);
  589. JANUS_LOG(LOG_VERB, " >> %d (%s)\n", ret, janus_get_api_error(ret));
  590. ps = ps->next;
  591. }
  592. g_free(leaving_text);
  593. g_list_free(participants_list);
  594. } else if(session->participant_type == janus_videoroom_p_type_subscriber) {
  595. /* Get rid of listener */
  596. janus_videoroom_listener *listener = (janus_videoroom_listener *)session->participant;
  597. janus_videoroom_participant *publisher = listener->feed;
  598. if(publisher != NULL) {
  599. janus_mutex_lock(&publisher->listeners_mutex);
  600. publisher->listeners = g_slist_remove(publisher->listeners, listener);
  601. janus_mutex_unlock(&publisher->listeners_mutex);
  602. listener->feed = NULL;
  603. }
  604. }
  605. }
  606. /* Thread to handle incoming messages */
  607. static void *janus_videoroom_handler(void *data) {
  608. JANUS_LOG(LOG_VERB, "Joining thread\n");
  609. janus_videoroom_message *msg = NULL;
  610. int error_code = 0;
  611. char *error_cause = calloc(512, sizeof(char)); /* FIXME 512 should be enough, but anyway... */
  612. if(error_cause == NULL) {
  613. JANUS_LOG(LOG_FATAL, "Memory error!\n");
  614. return NULL;
  615. }
  616. while(initialized && !stopping) {
  617. if(!messages || (msg = g_queue_pop_head(messages)) == NULL) {
  618. usleep(50000);
  619. continue;
  620. }
  621. janus_videoroom_session *session = (janus_videoroom_session *)msg->handle->plugin_handle;
  622. if(!session) {
  623. JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
  624. janus_videoroom_message_free(msg);
  625. continue;
  626. }
  627. if(session->destroy) {
  628. janus_videoroom_message_free(msg);
  629. continue;
  630. }
  631. /* Handle request */
  632. error_code = 0;
  633. JANUS_LOG(LOG_VERB, "Handling message: %s\n", msg->message);
  634. if(msg->message == NULL) {
  635. JANUS_LOG(LOG_ERR, "No message??\n");
  636. error_code = JANUS_VIDEOROOM_ERROR_NO_MESSAGE;
  637. sprintf(error_cause, "%s", "No message??");
  638. goto error;
  639. }
  640. json_error_t error;
  641. json_t *root = json_loads(msg->message, 0, &error);
  642. if(!root) {
  643. JANUS_LOG(LOG_ERR, "JSON error: on line %d: %s\n", error.line, error.text);
  644. error_code = JANUS_VIDEOROOM_ERROR_INVALID_JSON;
  645. sprintf(error_cause, "JSON error: on line %d: %s", error.line, error.text);
  646. goto error;
  647. }
  648. if(!json_is_object(root)) {
  649. JANUS_LOG(LOG_ERR, "JSON error: not an object\n");
  650. error_code = JANUS_VIDEOROOM_ERROR_INVALID_JSON;
  651. sprintf(error_cause, "JSON error: not an object");
  652. goto error;
  653. }
  654. /* Get the request first */
  655. json_t *request = json_object_get(root, "request");
  656. if(!request) {
  657. JANUS_LOG(LOG_ERR, "Missing element (request)\n");
  658. error_code = JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT;
  659. sprintf(error_cause, "Missing element (request)");
  660. goto error;
  661. }
  662. if(!json_is_string(request)) {
  663. JANUS_LOG(LOG_ERR, "Invalid element (request should be a string)\n");
  664. error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
  665. sprintf(error_cause, "Invalid element (request should be a string)");
  666. goto error;
  667. }
  668. const char *request_text = json_string_value(request);
  669. json_t *event = NULL;
  670. if(!strcasecmp(request_text, "create")) {
  671. /* Create a new videoroom */
  672. JANUS_LOG(LOG_VERB, "Creating a new videoroom\n");
  673. json_t *desc = json_object_get(root, "description");
  674. if(desc && !json_is_string(desc)) {
  675. JANUS_LOG(LOG_ERR, "Invalid element (description should be a string)\n");
  676. error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
  677. sprintf(error_cause, "Invalid element (description should be a string)");
  678. goto error;
  679. }
  680. json_t *bitrate = json_object_get(root, "bitrate");
  681. if(bitrate && !json_is_integer(bitrate)) {
  682. JANUS_LOG(LOG_ERR, "Invalid element (bitrate should be an integer)\n");
  683. error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
  684. sprintf(error_cause, "Invalid element (bitrate should be an integer)");
  685. goto error;
  686. }
  687. json_t *fir_freq = json_object_get(root, "fir_freq");
  688. if(fir_freq && !json_is_integer(fir_freq)) {
  689. JANUS_LOG(LOG_ERR, "Invalid element (fir_freq should be an integer)\n");
  690. error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
  691. sprintf(error_cause, "Invalid element (fir_freq should be an integer)");
  692. goto error;
  693. }
  694. json_t *publishers = json_object_get(root, "publishers");
  695. if(publishers && !json_is_integer(publishers)) {
  696. JANUS_LOG(LOG_ERR, "Invalid element (publishers should be an integer)\n");
  697. error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
  698. sprintf(error_cause, "Invalid element (publishers should be an integer)");
  699. goto error;
  700. }
  701. /* Create the audio bridge room */
  702. janus_videoroom *videoroom = calloc(1, sizeof(janus_videoroom));
  703. if(videoroom == NULL) {
  704. JANUS_LOG(LOG_FATAL, "Memory error!\n");
  705. error_code = JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR;
  706. sprintf(error_cause, "Memory error");
  707. goto error;
  708. }
  709. /* Generate a random ID */
  710. janus_mutex_lock(&rooms_mutex);
  711. guint64 room_id = 0;
  712. while(room_id == 0) {
  713. room_id = g_random_int();
  714. if(g_hash_table_lookup(rooms, GUINT_TO_POINTER(room_id)) != NULL) {
  715. /* Room ID already taken, try another one */
  716. room_id = 0;
  717. }
  718. }
  719. videoroom->room_id = room_id;
  720. char *description = NULL;
  721. if(desc != NULL) {
  722. description = g_strdup(json_string_value(desc));
  723. } else {
  724. char roomname[255];
  725. sprintf(roomname, "Room %"SCNu64"", videoroom->room_id);
  726. description = g_strdup(roomname);
  727. }
  728. if(description == NULL) {
  729. janus_mutex_unlock(&rooms_mutex);
  730. JANUS_LOG(LOG_FATAL, "Memory error!\n");
  731. continue;
  732. }
  733. videoroom->room_name = description;
  734. videoroom->max_publishers = 3; /* FIXME How should we choose a default? */
  735. if(publishers)
  736. videoroom->max_publishers = json_integer_value(publishers);
  737. if(videoroom->max_publishers < 0)
  738. videoroom->max_publishers = 3; /* FIXME How should we choose a default? */
  739. videoroom->bitrate = 0;
  740. if(bitrate)
  741. videoroom->bitrate = json_integer_value(bitrate);
  742. if(videoroom->bitrate > 0 && videoroom->bitrate < 64000)
  743. videoroom->bitrate = 64000; /* Don't go below 64k */
  744. videoroom->fir_freq = 0;
  745. if(fir_freq)
  746. videoroom->fir_freq = json_integer_value(fir_freq);
  747. videoroom->destroy = 0;
  748. videoroom->participants = g_hash_table_new(NULL, NULL);
  749. g_hash_table_insert(rooms, GUINT_TO_POINTER(videoroom->room_id), videoroom);
  750. JANUS_LOG(LOG_VERB, "Created videoroom: %"SCNu64" (%s)\n", videoroom->room_id, videoroom->room_name);
  751. /* Show updated rooms list */
  752. GList *rooms_list = g_hash_table_get_values(rooms);
  753. GList *r = rooms_list;
  754. while(r) {
  755. janus_videoroom *vr = (janus_videoroom *)r->data;
  756. JANUS_LOG(LOG_VERB, " ::: [%"SCNu64"][%s] %"SCNu64", max %d publishers, FIR frequency of %d seconds\n", vr->room_id, vr->room_name, vr->bitrate, vr->max_publishers, vr->fir_freq);
  757. r = r->next;
  758. }
  759. g_list_free(rooms_list);
  760. janus_mutex_unlock(&rooms_mutex);
  761. /* Send info back */
  762. event = json_object();
  763. json_object_set_new(event, "videoroom", json_string("created"));
  764. json_object_set_new(event, "room", json_integer(videoroom->room_id));
  765. } else
  766. /* What kind of participant is this session referring to? */
  767. if(session->participant_type == janus_videoroom_p_type_none) {
  768. JANUS_LOG(LOG_VERB, "Configuring new participant\n");
  769. /* Not configured yet, we need to do this now */
  770. if(strcasecmp(request_text, "join")) {
  771. JANUS_LOG(LOG_ERR, "Invalid request on unconfigured participant\n");
  772. error_code = JANUS_VIDEOROOM_ERROR_JOIN_FIRST;
  773. sprintf(error_cause, "Invalid request on unconfigured participant");
  774. goto error;
  775. }
  776. json_t *room = json_object_get(root, "room");
  777. if(!room) {
  778. JANUS_LOG(LOG_ERR, "Missing element (room)\n");
  779. sprintf(error_cause, "Missing element (room)");
  780. goto error;
  781. }
  782. if(!json_is_integer(room)) {
  783. JANUS_LOG(LOG_ERR, "Invalid element (room should be an integer)\n");
  784. error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
  785. sprintf(error_cause, "Invalid element (room should be an integer)");
  786. goto error;
  787. }
  788. guint64 room_id = json_integer_value(room);
  789. janus_mutex_lock(&rooms_mutex);
  790. janus_videoroom *videoroom = g_hash_table_lookup(rooms, GUINT_TO_POINTER(room_id));
  791. if(videoroom == NULL) {
  792. janus_mutex_unlock(&rooms_mutex);
  793. JANUS_LOG(LOG_ERR, "No such room (%"SCNu64")\n", room_id);
  794. error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM;
  795. sprintf(error_cause, "No such room (%"SCNu64")", room_id);
  796. goto error;
  797. }
  798. janus_mutex_unlock(&rooms_mutex);
  799. json_t *ptype = json_object_get(root, "ptype");
  800. if(!ptype) {
  801. JANUS_LOG(LOG_ERR, "Missing element (ptype)\n");
  802. error_code = JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT;
  803. sprintf(error_cause, "Missing element (ptype)");
  804. goto error;
  805. }
  806. if(!json_is_string(ptype)) {
  807. JANUS_LOG(LOG_ERR, "Invalid element (ptype should be a string)\n");
  808. error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
  809. sprintf(error_cause, "Invalid element (ptype should be a string)");
  810. goto error;
  811. }
  812. const char *ptype_text = json_string_value(ptype);
  813. if(!strcasecmp(ptype_text, "publisher")) {
  814. JANUS_LOG(LOG_VERB, "Configuring new publisher\n");
  815. json_t *display = json_object_get(root, "display");
  816. if(!display) {
  817. JANUS_LOG(LOG_ERR, "Missing element (display)\n");
  818. error_code = JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT;
  819. sprintf(error_cause, "Missing element (display)");
  820. goto error;
  821. }
  822. if(!json_is_string(display)) {
  823. JANUS_LOG(LOG_ERR, "Invalid element (display should be a string)\n");
  824. error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
  825. sprintf(error_cause, "Invalid element (display should be a string)");
  826. goto error;
  827. }
  828. const char *display_text = json_string_value(display);
  829. /* Generate a random ID */
  830. guint64 user_id = 0;
  831. while(user_id == 0) {
  832. user_id = g_random_int();
  833. if(g_hash_table_lookup(videoroom->participants, GUINT_TO_POINTER(user_id)) != NULL) {
  834. /* User ID already taken, try another one */
  835. user_id = 0;
  836. }
  837. }
  838. JANUS_LOG(LOG_VERB, " -- Publisher ID: %"SCNu64"\n", user_id);
  839. janus_videoroom_participant *publisher = calloc(1, sizeof(janus_videoroom_participant));
  840. if(publisher == NULL) {
  841. JANUS_LOG(LOG_FATAL, "Memory error!\n");
  842. error_code = JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR;
  843. sprintf(error_cause, "Memory error");
  844. goto error;
  845. }
  846. publisher->session = session;
  847. publisher->room = videoroom;
  848. publisher->user_id = user_id;
  849. publisher->display = g_strdup(display_text);
  850. if(publisher->display == NULL) {
  851. JANUS_LOG(LOG_FATAL, "Memory error!\n");
  852. error_code = JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR;
  853. sprintf(error_cause, "Memory error");
  854. g_free(publisher);
  855. goto error;
  856. }
  857. publisher->sdp = NULL; /* We'll deal with this later */
  858. publisher->audio_active = FALSE;
  859. publisher->video_active = FALSE;
  860. publisher->firefox = FALSE;
  861. publisher->bitrate = videoroom->bitrate;
  862. publisher->listeners = NULL;
  863. janus_mutex_init(&publisher->listeners_mutex);
  864. publisher->fir_latest = 0;
  865. publisher->fir_seq = 0;
  866. /* Done */
  867. session->participant_type = janus_videoroom_p_type_publisher;
  868. session->participant = publisher;
  869. g_hash_table_insert(videoroom->participants, GUINT_TO_POINTER(user_id), publisher);
  870. /* Return a list of all available publishers (those with an SDP available, that is) */
  871. json_t *list = json_array();
  872. GList *participants_list = g_hash_table_get_values(videoroom->participants);
  873. GList *ps = participants_list;
  874. while(ps) {
  875. janus_videoroom_participant *p = (janus_videoroom_participant *)ps->data;
  876. if(p == publisher || !p->sdp) {
  877. ps = ps->next;
  878. continue;
  879. }
  880. json_t *pl = json_object();
  881. json_object_set_new(pl, "id", json_integer(p->user_id));
  882. json_object_set_new(pl, "display", json_string(p->display));
  883. json_array_append_new(list, pl);
  884. ps = ps->next;
  885. }
  886. event = json_object();
  887. json_object_set_new(event, "videoroom", json_string("joined"));
  888. json_object_set_new(event, "room", json_integer(videoroom->room_id));
  889. json_object_set_new(event, "description", json_string(videoroom->room_name));
  890. json_object_set_new(event, "id", json_integer(user_id));
  891. json_object_set_new(event, "publishers", list);
  892. g_list_free(participants_list);
  893. } else if(!strcasecmp(ptype_text, "listener")) {
  894. JANUS_LOG(LOG_VERB, "Configuring new listener\n");
  895. /* This is a new listener */
  896. json_t *feed = json_object_get(root, "feed");
  897. if(!feed) {
  898. JANUS_LOG(LOG_ERR, "Missing element (feed)\n");
  899. error_code = JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT;
  900. sprintf(error_cause, "Missing element (feed)");
  901. goto error;
  902. }
  903. if(!json_is_integer(feed)) {
  904. JANUS_LOG(LOG_ERR, "Invalid element (feed should be an integer)\n");
  905. error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
  906. sprintf(error_cause, "Invalid element (feed should be an integer)");
  907. goto error;
  908. }
  909. guint64 feed_id = json_integer_value(feed);
  910. janus_videoroom_participant *publisher = g_hash_table_lookup(videoroom->participants, GUINT_TO_POINTER(feed_id));
  911. if(publisher == NULL || publisher->sdp == NULL) {
  912. JANUS_LOG(LOG_ERR, "No such feed (%"SCNu64")\n", feed_id);
  913. error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_FEED;
  914. sprintf(error_cause, "No such feed (%"SCNu64")", feed_id);
  915. goto error;
  916. } else {
  917. janus_videoroom_listener *listener = calloc(1, sizeof(janus_videoroom_listener));
  918. if(listener == NULL) {
  919. JANUS_LOG(LOG_FATAL, "Memory error!\n");
  920. error_code = JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR;
  921. sprintf(error_cause, "Memory error");
  922. goto error;
  923. }
  924. listener->session = session;
  925. listener->room = videoroom;
  926. listener->feed = publisher;
  927. listener->paused = TRUE; /* We need an explicit start from the listener */
  928. session->participant = listener;
  929. janus_mutex_lock(&publisher->listeners_mutex);
  930. publisher->listeners = g_slist_append(publisher->listeners, listener);
  931. janus_mutex_unlock(&publisher->listeners_mutex);
  932. event = json_object();
  933. json_object_set_new(event, "videoroom", json_string("attached"));
  934. json_object_set_new(event, "room", json_integer(videoroom->room_id));
  935. json_object_set_new(event, "id", json_integer(feed_id));
  936. json_object_set_new(event, "display", json_string(publisher->display));
  937. session->participant_type = janus_videoroom_p_type_subscriber;
  938. JANUS_LOG(LOG_VERB, "Preparing JSON event as a reply\n");
  939. char *event_text = json_dumps(event, JSON_INDENT(3));
  940. json_decref(event);
  941. /* Negotiate by sending the selected publisher SDP back */
  942. if(publisher->sdp != NULL) {
  943. /* How long will the gateway take to push the event? */
  944. gint64 start = janus_get_monotonic_time();
  945. int res = gateway->push_event(msg->handle, &janus_videoroom_plugin, msg->transaction, event_text, "offer", publisher->sdp);
  946. JANUS_LOG(LOG_VERB, " >> Pushing event: %d (took %"SCNu64" us)\n", res, janus_get_monotonic_time()-start);
  947. JANUS_LOG(LOG_VERB, " >> %d\n", res);
  948. g_free(event_text);
  949. json_decref(root);
  950. continue;
  951. }
  952. g_free(event_text);
  953. }
  954. } else {
  955. JANUS_LOG(LOG_ERR, "Invalid element (ptype)\n");
  956. error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
  957. sprintf(error_cause, "Invalid element (ptype)");
  958. goto error;
  959. }
  960. } else if(session->participant_type == janus_videoroom_p_type_publisher) {
  961. /* Handle this publisher */
  962. janus_videoroom_participant *participant = (janus_videoroom_participant *)session->participant;
  963. if(!strcasecmp(request_text, "join")) {
  964. JANUS_LOG(LOG_ERR, "Already in as a publisher on this handle\n");
  965. error_code = JANUS_VIDEOROOM_ERROR_ALREADY_JOINED;
  966. sprintf(error_cause, "Already in as a publisher on this handle");
  967. goto error;
  968. } else if(!strcasecmp(request_text, "configure")) {
  969. /* Configure audio/video/bitrate for this publisher */
  970. json_t *audio = json_object_get(root, "audio");
  971. if(audio && !json_is_boolean(audio)) {
  972. JANUS_LOG(LOG_ERR, "Invalid element (audio should be a boolean)\n");
  973. error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
  974. sprintf(error_cause, "Invalid value (audio should be a boolean)");
  975. goto error;
  976. }
  977. json_t *video = json_object_get(root, "video");
  978. if(video && !json_is_boolean(video)) {
  979. JANUS_LOG(LOG_ERR, "Invalid element (video should be a boolean)\n");
  980. error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
  981. sprintf(error_cause, "Invalid value (video should be a boolean)");
  982. goto error;
  983. }
  984. json_t *bitrate = json_object_get(root, "bitrate");
  985. if(bitrate && !json_is_integer(bitrate)) {
  986. JANUS_LOG(LOG_ERR, "Invalid element (bitrate should be an integer)\n");
  987. error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
  988. sprintf(error_cause, "Invalid value (bitrate should be an integer)");
  989. goto error;
  990. }
  991. if(audio) {
  992. participant->audio_active = json_is_true(audio);
  993. JANUS_LOG(LOG_VERB, "Setting audio property: %s (room %"SCNu64", user %"SCNu64")\n", participant->audio_active ? "true" : "false", participant->room->room_id, participant->user_id);
  994. }
  995. if(video) {
  996. participant->video_active = json_is_true(video);
  997. JANUS_LOG(LOG_VERB, "Setting video property: %s (room %"SCNu64", user %"SCNu64")\n", participant->video_active ? "true" : "false", participant->room->room_id, participant->user_id);
  998. }
  999. if(bitrate) {
  1000. participant->bitrate = json_integer_value(bitrate);
  1001. JANUS_LOG(LOG_VERB, "Setting video bitrate: %"SCNu64" (room %"SCNu64", user %"SCNu64")\n", participant->bitrate, participant->room->room_id, participant->user_id);
  1002. }
  1003. /* Done */
  1004. event = json_object();
  1005. json_object_set_new(event, "videoroom", json_string("event"));
  1006. json_object_set_new(event, "room", json_integer(participant->room->room_id));
  1007. json_object_set_new(event, "configured", json_string("ok"));
  1008. } else if(!strcasecmp(request_text, "leave")) {
  1009. /* This publisher is leaving, tell everybody */
  1010. event = json_object();
  1011. json_object_set_new(event, "videoroom", json_string("event"));
  1012. json_object_set_new(event, "room", json_integer(participant->room->room_id));
  1013. json_object_set_new(event, "leaving", json_integer(participant->user_id));
  1014. char *leaving_text = json_dumps(event, JSON_INDENT(3));
  1015. GList *participants_list = g_hash_table_get_values(participant->room->participants);
  1016. GList *ps = participants_list;
  1017. while(ps) {
  1018. janus_videoroom_participant *p = (janus_videoroom_participant *)ps->data;
  1019. if(p == participant) {
  1020. ps = ps->next;
  1021. continue; /* Skip the new publisher itself */
  1022. }
  1023. JANUS_LOG(LOG_VERB, "Notifying participant %"SCNu64" (%s)\n", p->user_id, p->display);
  1024. int ret = gateway->push_event(p->session->handle, &janus_videoroom_plugin, NULL, leaving_text, NULL, NULL);
  1025. JANUS_LOG(LOG_VERB, " >> %d (%s)\n", ret, janus_get_api_error(ret));
  1026. ps = ps->next;
  1027. }
  1028. g_free(leaving_text);
  1029. g_list_free(participants_list);
  1030. /* Done */
  1031. participant->audio_active = 0;
  1032. participant->video_active = 0;
  1033. session->started = FALSE;
  1034. session->destroy = TRUE;
  1035. } else {
  1036. JANUS_LOG(LOG_ERR, "Unknown request '%s'\n", request_text);
  1037. error_code = JANUS_VIDEOROOM_ERROR_INVALID_REQUEST;
  1038. sprintf(error_cause, "Unknown request '%s'", request_text);
  1039. goto error;
  1040. }
  1041. } else if(session->participant_type == janus_videoroom_p_type_subscriber) {
  1042. /* Handle this listener */
  1043. janus_videoroom_listener *listener = (janus_videoroom_listener *)session->participant;
  1044. if(!strcasecmp(request_text, "join")) {
  1045. JANUS_LOG(LOG_ERR, "Already in as a publisher on this handle\n");
  1046. error_code = JANUS_VIDEOROOM_ERROR_ALREADY_JOINED;
  1047. sprintf(error_cause, "Already in as a listener on this handle");
  1048. goto error;
  1049. } else if(!strcasecmp(request_text, "start")) {
  1050. /* Start/restart receiving the publisher streams */
  1051. janus_videoroom_participant *publisher = listener->feed;
  1052. listener->paused = FALSE;
  1053. event = json_object();
  1054. json_object_set_new(event, "videoroom", json_string("event"));
  1055. json_object_set_new(event, "room", json_integer(publisher->room->room_id));
  1056. json_object_set_new(event, "started", json_string("ok"));
  1057. /* Send a FIR */
  1058. char buf[20];
  1059. memset(buf, 0, 20);
  1060. if(!publisher->firefox)
  1061. janus_rtcp_fir((char *)&buf, 20, &publisher->fir_seq);
  1062. else
  1063. janus_rtcp_fir_legacy((char *)&buf, 20, &publisher->fir_seq);
  1064. JANUS_LOG(LOG_VERB, "Resuming publisher, sending FIR to %s\n", publisher->display);
  1065. gateway->relay_rtcp(publisher->session->handle, 1, buf, 20);
  1066. /* Send a PLI too, just in case... */
  1067. memset(buf, 0, 12);
  1068. janus_rtcp_pli((char *)&buf, 12);
  1069. JANUS_LOG(LOG_VERB, "Resuming publisher, sending PLI to %s\n", publisher->display);
  1070. gateway->relay_rtcp(publisher->session->handle, 1, buf, 12);
  1071. } else if(!strcasecmp(request_text, "pause")) {
  1072. /* Stop receiving the publisher streams for a while */
  1073. janus_videoroom_participant *publisher = listener->feed;
  1074. listener->paused = TRUE;
  1075. event = json_object();
  1076. json_object_set_new(event, "videoroom", json_string("event"));
  1077. json_object_set_new(event, "room", json_integer(publisher->room->room_id));
  1078. json_object_set_new(event, "paused", json_string("ok"));
  1079. } else if(!strcasecmp(request_text, "leave")) {
  1080. janus_videoroom_participant *publisher = listener->feed;
  1081. if(publisher != NULL) {
  1082. janus_mutex_lock(&publisher->listeners_mutex);
  1083. publisher->listeners = g_slist_remove(publisher->listeners, listener);
  1084. janus_mutex_unlock(&publisher->listeners_mutex);
  1085. listener->feed = NULL;
  1086. }
  1087. event = json_object();
  1088. json_object_set_new(event, "videoroom", json_string("event"));
  1089. json_object_set_new(event, "room", json_integer(publisher->room->room_id));
  1090. json_object_set_new(event, "left", json_string("ok"));
  1091. session->started = FALSE;
  1092. } else {
  1093. JANUS_LOG(LOG_ERR, "Unknown request '%s'\n", request_text);
  1094. error_code = JANUS_VIDEOROOM_ERROR_INVALID_REQUEST;
  1095. sprintf(error_cause, "Unknown request '%s'", request_text);
  1096. goto error;
  1097. }
  1098. }
  1099. json_decref(root);
  1100. /* Prepare JSON event */
  1101. JANUS_LOG(LOG_VERB, "Preparing JSON event as a reply\n");
  1102. char *event_text = json_dumps(event, JSON_INDENT(3));
  1103. json_decref(event);
  1104. /* Any SDP to handle? */
  1105. if(!msg->sdp) {
  1106. int ret = gateway->push_event(msg->handle, &janus_videoroom_plugin, msg->transaction, event_text, NULL, NULL);
  1107. JANUS_LOG(LOG_VERB, " >> %d (%s)\n", ret, janus_get_api_error(ret));
  1108. } else {
  1109. JANUS_LOG(LOG_VERB, "This is involving a negotiation (%s) as well:\n%s\n", msg->sdp_type, msg->sdp);
  1110. char *type = NULL;
  1111. if(!strcasecmp(msg->sdp_type, "offer")) {
  1112. /* We need to answer */
  1113. type = "answer";
  1114. } else if(!strcasecmp(msg->sdp_type, "answer")) {
  1115. /* We got an answer (from a listener?), no need to negotiate */
  1116. int ret = gateway->push_event(msg->handle, &janus_videoroom_plugin, msg->transaction, event_text, NULL, NULL);
  1117. JANUS_LOG(LOG_VERB, " >> %d (%s)\n", ret, janus_get_api_error(ret));
  1118. } else {
  1119. /* TODO We don't support anything else right now... */
  1120. JANUS_LOG(LOG_ERR, "Unknown SDP type '%s'\n", msg->sdp_type);
  1121. error_code = JANUS_VIDEOROOM_ERROR_INVALID_SDP_TYPE;
  1122. sprintf(error_cause, "Unknown SDP type '%s'", msg->sdp_type);
  1123. goto error;
  1124. }
  1125. if(session->participant_type == janus_videoroom_p_type_publisher) {
  1126. /* This is a new publisher: is there room? */
  1127. janus_videoroom_participant *participant = (janus_videoroom_participant *)session->participant;
  1128. janus_videoroom *videoroom = participant->room;
  1129. GList *participants_list = g_hash_table_get_values(videoroom->participants);
  1130. GList *ps = participants_list;
  1131. int count = 0;
  1132. while(ps) {
  1133. janus_videoroom_participant *p = (janus_videoroom_participant *)ps->data;
  1134. if(p != participant && p->sdp)
  1135. count++;
  1136. ps = ps->next;
  1137. }
  1138. if(count == videoroom->max_publishers) {
  1139. g_list_free(participants_list);
  1140. participant->audio_active = FALSE;
  1141. participant->video_active = FALSE;
  1142. JANUS_LOG(LOG_ERR, "Maximum number of publishers (%d) already reached\n", videoroom->max_publishers);
  1143. error_code = JANUS_VIDEOROOM_ERROR_PUBLISHERS_FULL;
  1144. sprintf(error_cause, "Maximum number of publishers (%d) already reached", videoroom->max_publishers);
  1145. goto error;
  1146. }
  1147. /* Now prepare the SDP to give back */
  1148. if(strstr(msg->sdp, "Mozilla")) {
  1149. participant->firefox = TRUE;
  1150. }
  1151. /* Which media are available? */
  1152. int audio = 0, video = 0;
  1153. if(strstr(msg->sdp, "m=audio")) {
  1154. audio++;
  1155. }
  1156. JANUS_LOG(LOG_VERB, "The publisher %s going to send an audio stream\n", audio ? "is" : "is NOT");
  1157. if(strstr(msg->sdp, "m=video")) {
  1158. video++;
  1159. }
  1160. JANUS_LOG(LOG_VERB, "The publisher %s going to send a video stream\n", video ? "is" : "is NOT");
  1161. /* What is the Opus payload type? */
  1162. int opus_pt = 0;
  1163. if(audio) {
  1164. char *fmtp = strstr(msg->sdp, "opus/48000");
  1165. if(fmtp != NULL) {
  1166. fmtp -= 5;
  1167. fmtp = strstr(fmtp, ":");
  1168. if(fmtp)
  1169. fmtp++;
  1170. opus_pt = atoi(fmtp);
  1171. }
  1172. JANUS_LOG(LOG_VERB, "Opus payload type is %d\n", opus_pt);
  1173. }
  1174. /* What is the VP8 payload type? */
  1175. int vp8_pt = 0;
  1176. if(video) {
  1177. char *fmtp = strstr(msg->sdp, "VP8/90000");
  1178. if(fmtp != NULL) {
  1179. fmtp -= 5;
  1180. fmtp = strstr(fmtp, ":");
  1181. if(fmtp)
  1182. fmtp++;
  1183. vp8_pt = atoi(fmtp);
  1184. }
  1185. JANUS_LOG(LOG_VERB, "VP8 payload type is %d\n", vp8_pt);
  1186. }
  1187. /* Also add a bandwidth SDP attribute if we're capping the bitrate in the room */
  1188. int b = (int)(videoroom->bitrate/1000);
  1189. char sdp[1024], audio_mline[256], video_mline[512];
  1190. if(audio) {
  1191. g_sprintf(audio_mline, sdp_a_template,
  1192. opus_pt, /* Opus payload type */
  1193. "recvonly", /* The publisher gets a recvonly back */
  1194. opus_pt); /* Opus payload type */
  1195. } else {
  1196. audio_mline[0] = '\0';
  1197. }
  1198. if(video) {
  1199. g_sprintf(video_mline, sdp_v_template,
  1200. vp8_pt, /* VP8 payload type */
  1201. b, /* Bandwidth */
  1202. "recvonly", /* The publisher gets a recvonly back */
  1203. vp8_pt, /* VP8 payload type */
  1204. vp8_pt, /* VP8 payload type */
  1205. vp8_pt, /* VP8 payload type */
  1206. vp8_pt, /* VP8 payload type */
  1207. vp8_pt); /* VP8 payload type */
  1208. } else {
  1209. audio_mline[0] = '\0';
  1210. }
  1211. g_sprintf(sdp, sdp_template,
  1212. janus_get_monotonic_time(), /* We need current time here */
  1213. janus_get_monotonic_time(), /* We need current time here */
  1214. participant->room->room_name, /* Video room name */
  1215. audio_mline, /* Audio m-line, if any */
  1216. video_mline); /* Video m-line, if any */
  1217. int modified = 0;
  1218. char *newsdp = g_strdup(sdp), *tempsdp = NULL;
  1219. if(video && b == 0) {
  1220. /* Remove useless bandwidth attribute */
  1221. tempsdp = string_replace(newsdp, "b=AS:0\r\n", "", &modified);
  1222. if(modified) {
  1223. g_free(newsdp);
  1224. newsdp = tempsdp;
  1225. }
  1226. }
  1227. JANUS_LOG(LOG_VERB, "Handling publisher: turned this into an '%s':\n%s\n", type, newsdp);
  1228. /* How long will the gateway take to push the event? */
  1229. gint64 start = janus_get_monotonic_time();
  1230. int res = gateway->push_event(msg->handle, &janus_videoroom_plugin, msg->transaction, event_text, type, newsdp);
  1231. JANUS_LOG(LOG_VERB, " >> Pushing event: %d (took %"SCNu64" us)\n", res, janus_get_monotonic_time()-start);
  1232. tempsdp = string_replace(newsdp, "recvonly", "sendonly", &modified);
  1233. if(modified) {
  1234. g_free(newsdp);
  1235. tempsdp = tempsdp;
  1236. }
  1237. if(res != JANUS_OK) {
  1238. /* TODO Failed to negotiate? We should remove this publisher */
  1239. } else {
  1240. /* Store the participant's SDP for interested listeners */
  1241. participant->sdp = newsdp;
  1242. /* Notify all other participants that there's a new boy in town */
  1243. json_t *list = json_array();
  1244. json_t *pl = json_object();
  1245. json_object_set_new(pl, "id", json_integer(participant->user_id));
  1246. json_object_set_new(pl, "display", json_string(participant->display));
  1247. json_array_append_new(list, pl);
  1248. json_t *pub = json_object();
  1249. json_object_set_new(pub, "videoroom", json_string("event"));
  1250. json_object_set_new(pub, "room", json_integer(participant->room->room_id));
  1251. json_object_set_new(pub, "publishers", list);
  1252. char *pub_text = json_dumps(pub, JSON_INDENT(3));
  1253. json_decref(pub);
  1254. GList *participants_list = g_hash_table_get_values(participant->room->participants);
  1255. GList *ps = participants_list;
  1256. while(ps) {
  1257. janus_videoroom_participant *p = (janus_videoroom_participant *)ps->data;
  1258. if(p == participant) {
  1259. ps = ps->next;
  1260. continue; /* Skip the new publisher itself */
  1261. }
  1262. JANUS_LOG(LOG_VERB, "Notifying participant %"SCNu64" (%s)\n", p->user_id, p->display);
  1263. int ret = gateway->push_event(p->session->handle, &janus_videoroom_plugin, NULL, pub_text, NULL, NULL);
  1264. JANUS_LOG(LOG_VERB, " >> %d (%s)\n", ret, janus_get_api_error(ret));
  1265. ps = ps->next;
  1266. }
  1267. g_list_free(participants_list);
  1268. /* Let's wait for the setup_media event */
  1269. }
  1270. } else if(session->participant_type == janus_videoroom_p_type_subscriber) {
  1271. /* Negotiate by sending the selected publisher SDP back */
  1272. janus_videoroom_listener *listener = (janus_videoroom_listener *)session->participant;
  1273. /* FIXME We should handle the case where the participant has no SDP... */
  1274. if(listener != NULL) {
  1275. janus_videoroom_participant *feed = (janus_videoroom_participant *)listener->feed;
  1276. if(feed != NULL && feed->sdp != NULL) {
  1277. /* How long will the gateway take to push the event? */
  1278. gint64 start = janus_get_monotonic_time();
  1279. int res = gateway->push_event(msg->handle, &janus_videoroom_plugin, msg->transaction, event_text, type, feed->sdp);
  1280. JANUS_LOG(LOG_VERB, " >> Pushing event: %d (took %"SCNu64" us)\n", res, janus_get_monotonic_time()-start);
  1281. if(res != JANUS_OK) {
  1282. /* TODO Failed to negotiate? We should remove this listener */
  1283. } else {
  1284. /* Let's wait for the setup_media event */
  1285. }
  1286. }
  1287. }
  1288. }
  1289. }
  1290. g_free(event_text);
  1291. janus_videoroom_message_free(msg);
  1292. continue;
  1293. error:
  1294. {
  1295. if(root != NULL)
  1296. json_decref(root);
  1297. /* Prepare JSON error event */
  1298. json_t *event = json_object();
  1299. json_object_set_new(event, "videoroom", json_string("event"));
  1300. json_object_set_new(event, "error_code", json_integer(error_code));
  1301. json_object_set_new(event, "error", json_string(error_cause));
  1302. char *event_text = json_dumps(event, JSON_INDENT(3));
  1303. json_decref(event);
  1304. JANUS_LOG(LOG_VERB, "Pushing event: %s\n", event_text);
  1305. int ret = gateway->push_event(msg->handle, &janus_videoroom_plugin, msg->transaction, event_text, NULL, NULL);
  1306. JANUS_LOG(LOG_VERB, " >> %d (%s)\n", ret, janus_get_api_error(ret));
  1307. g_free(event_text);
  1308. janus_videoroom_message_free(msg);
  1309. }
  1310. }
  1311. g_free(error_cause);
  1312. JANUS_LOG(LOG_VERB, "Leaving thread\n");
  1313. return NULL;
  1314. }
  1315. static void janus_videoroom_relay_rtp_packet(gpointer data, gpointer user_data) {
  1316. janus_videoroom_rtp_relay_packet *packet = (janus_videoroom_rtp_relay_packet *)user_data;
  1317. if(!packet || !packet->data || packet->length < 1) {
  1318. JANUS_LOG(LOG_ERR, "Invalid packet...\n");
  1319. return;
  1320. }
  1321. janus_videoroom_listener *listener = (janus_videoroom_listener *)data;
  1322. if(!listener || !listener->session) {
  1323. // JANUS_LOG(LOG_ERR, "Invalid session...\n");
  1324. return;
  1325. }
  1326. if(listener->paused) {
  1327. // JANUS_LOG(LOG_ERR, "This listener paused the stream...\n");
  1328. return;
  1329. }
  1330. janus_videoroom_session *session = listener->session;
  1331. if(!session || !session->handle) {
  1332. // JANUS_LOG(LOG_ERR, "Invalid session...\n");
  1333. return;
  1334. }
  1335. if(!session->started) {
  1336. // JANUS_LOG(LOG_ERR, "Streaming not started yet for this session...\n");
  1337. return;
  1338. }
  1339. if(gateway != NULL) /* FIXME What about RTCP? */
  1340. gateway->relay_rtp(session->handle, packet->is_video, (char *)packet->data, packet->length);
  1341. return;
  1342. }
  1343. /* Easy way to replace multiple occurrences of a string with another: ALWAYS creates a NEW string */
  1344. char *string_replace(char *message, char *old, char *new, int *modified)
  1345. {
  1346. if(!message || !old || !new || !modified)
  1347. return NULL;
  1348. *modified = 0;
  1349. if(!strstr(message, old)) { /* Nothing to be done (old is not there) */
  1350. return message;
  1351. }
  1352. if(!strcmp(old, new)) { /* Nothing to be done (old=new) */
  1353. return message;
  1354. }
  1355. if(strlen(old) == strlen(new)) { /* Just overwrite */
  1356. char *outgoing = message;
  1357. char *pos = strstr(outgoing, old), *tmp = NULL;
  1358. int i = 0;
  1359. while(pos) {
  1360. i++;
  1361. memcpy(pos, new, strlen(new));
  1362. pos += strlen(old);
  1363. tmp = strstr(pos, old);
  1364. pos = tmp;
  1365. }
  1366. return outgoing;
  1367. } else { /* We need to resize */
  1368. *modified = 1;
  1369. char *outgoing = strdup(message);
  1370. if(outgoing == NULL) {
  1371. JANUS_LOG(LOG_FATAL, "Memory error!\n");
  1372. return NULL;
  1373. }
  1374. int diff = strlen(new) - strlen(old);
  1375. /* Count occurrences */
  1376. int counter = 0;
  1377. char *pos = strstr(outgoing, old), *tmp = NULL;
  1378. while(pos) {
  1379. counter++;
  1380. pos += strlen(old);
  1381. tmp = strstr(pos, old);
  1382. pos = tmp;
  1383. }
  1384. uint16_t oldlen = strlen(outgoing)+1, newlen = oldlen + diff*counter;
  1385. *modified = diff*counter;
  1386. if(diff > 0) { /* Resize now */
  1387. tmp = realloc(outgoing, newlen);
  1388. if(!tmp)
  1389. return NULL;
  1390. outgoing = tmp;
  1391. }
  1392. /* Replace string */
  1393. pos = strstr(outgoing, old);
  1394. while(pos) {
  1395. if(diff > 0) { /* Move to the right (new is larger than old) */
  1396. uint16_t len = strlen(pos)+1;
  1397. memmove(pos + diff, pos, len);
  1398. memcpy(pos, new, strlen(new));
  1399. pos += strlen(new);
  1400. tmp = strstr(pos, old);
  1401. } else { /* Move to the left (new is smaller than old) */
  1402. uint16_t len = strlen(pos - diff)+1;
  1403. memmove(pos, pos - diff, len);
  1404. memcpy(pos, new, strlen(new));
  1405. pos += strlen(old);
  1406. tmp = strstr(pos, old);
  1407. }
  1408. pos = tmp;
  1409. }
  1410. if(diff < 0) { /* We skipped the resize previously (shrinking memory) */
  1411. tmp = realloc(outgoing, newlen);
  1412. if(!tmp)
  1413. return NULL;
  1414. outgoing = tmp;
  1415. }
  1416. outgoing[strlen(outgoing)] = '\0';
  1417. return outgoing;
  1418. }
  1419. }