· 6 years ago · Sep 16, 2019, 09:46 PM
1/*! \file janus_videoroom.c
2 * \author Lorenzo Miniero <lorenzo@meetecho.com>
3 * \copyright GNU General Public License v3
4 * \brief Janus VideoRoom plugin
5 * \details Check the \ref videoroom for more details.
6 *
7 * \ingroup plugins
8 * \ref plugins
9 *
10 * \page videoroom VideoRoom plugin documentation
11 * This is a plugin implementing a videoconferencing SFU
12 * (Selective Forwarding Unit) for Janus, that is an audio/video router.
13 * This means that the plugin implements a virtual conferencing room peers
14 * can join and leave at any time. This room is based on a Publish/Subscribe
15 * pattern. Each peer can publish his/her own live audio/video feeds: this
16 * feed becomes an available stream in the room the other participants can
17 * attach to. This means that this plugin allows the realization of several
18 * different scenarios, ranging from a simple webinar (one speaker, several
19 * watchers) to a fully meshed video conference (each peer sending and
20 * receiving to and from all the others).
21 *
22 * Considering that this plugin allows for several different WebRTC PeerConnections
23 * to be on at the same time for the same peer (specifically, each peer
24 * potentially has 1 PeerConnection on for publishing and N on for subscriptions
25 * from other peers), each peer may need to attach several times to the same
26 * plugin for every stream: this means that each peer needs to have at least one
27 * handle active for managing its relation with the plugin (joining a room,
28 * leaving a room, muting/unmuting, publishing, receiving events), and needs
29 * to open a new one each time he/she wants to subscribe to a feed from
30 * another publisher participant. The handle used for a subscription,
31 * however, would be logically a "slave" to the master one used for
32 * managing the room: this means that it cannot be used, for instance,
33 * to unmute in the room, as its only purpose would be to provide a
34 * context in which creating the recvonly PeerConnection for the
35 * subscription to an active publisher participant.
36 *
37 * \note Work is going on to implement SSRC multiplexing (Unified Plan),
38 * meaning that in the future you'll be able to use the same
39 * Janus handle/VideoRoom subscriber/PeerConnection to receive multiple
40 * publishers at the same time.
41 *
42 * Rooms to make available are listed in the plugin configuration file.
43 * A pre-filled configuration file is provided in \c conf/janus.plugin.videoroom.jcfg
44 * and includes a demo room for testing. The same plugin is also used
45 * dynamically (that is, with rooms created on the fly via API) in the
46 * Screen Sharing demo as well.
47 *
48 * To add more rooms or modify the existing one, you can use the following
49 * syntax:
50 *
51 * \verbatim
52room-<unique room ID>: {
53 description = This is my awesome room
54 is_private = true|false (private rooms don't appear when you do a 'list' request)
55 secret = <optional password needed for manipulating (e.g. destroying) the room>
56 pin = <optional password needed for joining the room>
57 require_pvtid = true|false (whether subscriptions are required to provide a valid
58 a valid private_id to associate with a publisher, default=false)
59 publishers = <max number of concurrent senders> (e.g., 6 for a video
60 conference or 1 for a webinar, default=3)
61 bitrate = <max video bitrate for senders> (e.g., 128000)
62 fir_freq = <send a FIR to publishers every fir_freq seconds> (0=disable)
63 audiocodec = opus|g722|pcmu|pcma|isac32|isac16 (audio codec to force on publishers, default=opus
64 can be a comma separated list in order of preference, e.g., opus,pcmu)
65 videocodec = vp8|vp9|h264 (video codec to force on publishers, default=vp8
66 can be a comma separated list in order of preference, e.g., vp9,vp8,h264)
67 opus_fec = true|false (whether inband FEC must be negotiated; only works for Opus, default=false)
68 video_svc = true|false (whether SVC support must be enabled; only works for VP9, default=false)
69 audiolevel_ext = true|false (whether the ssrc-audio-level RTP extension must be
70 negotiated/used or not for new publishers, default=true)
71 audiolevel_event = true|false (whether to emit event to other users or not)
72 audio_active_packets = 100 (number of packets with audio level, default=100, 2 seconds)
73 audio_level_average = 25 (average value of audio level, 127=muted, 0='too loud', default=25)
74 videoorient_ext = true|false (whether the video-orientation RTP extension must be
75 negotiated/used or not for new publishers, default=true)
76 playoutdelay_ext = true|false (whether the playout-delay RTP extension must be
77 negotiated/used or not for new publishers, default=true)
78 transport_wide_cc_ext = true|false (whether the transport wide CC RTP extension must be
79 negotiated/used or not for new publishers, default=true)
80 record = true|false (whether this room should be recorded, default=false)
81 rec_dir = <folder where recordings should be stored, when enabled>
82 notify_joining = true|false (optional, whether to notify all participants when a new
83 participant joins the room. The Videoroom plugin by design only notifies
84 new feeds (publishers), and enabling this may result extra notification
85 traffic. This flag is particularly useful when enabled with \c require_pvtid
86 for admin to manage listening only participants. default=false)
87}
88\endverbatim
89 *
90 * Note that recording will work with all codecs except iSAC.
91 *
92 * \section sfuapi Video Room API
93 *
94 * The Video Room API supports several requests, some of which are
95 * synchronous and some asynchronous. There are some situations, though,
96 * (invalid JSON, invalid request) which will always result in a
97 * synchronous error response even for asynchronous requests.
98 *
99 * \c create , \c destroy , \c edit , \c exists, \c list, \c allowed, \c kick and
100 * and \c listparticipants are synchronous requests, which means you'll
101 * get a response directly within the context of the transaction.
102 * \c create allows you to create a new video room dynamically, as an
103 * alternative to using the configuration file; \c edit allows you to
104 * dynamically edit some room properties (e.g., the PIN); \c destroy removes a
105 * video room and destroys it, kicking all the users out as part of the
106 * process; \c exists allows you to check whether a specific video room
107 * exists; finally, \c list lists all the available rooms, while \c
108 * listparticipants lists all the active (as in currentòy publishing
109 * something) participants of a specific room and their details.
110 *
111 * The \c join , \c joinandconfigure , \c configure , \c publish ,
112 * \c unpublish , \c start , \c pause , \c switch and \c leave
113 * requests instead are all asynchronous, which
114 * means you'll get a notification about their success or failure in
115 * an event. \c join allows you to join a specific video room, specifying
116 * whether that specific PeerConnection will be used for publishing or
117 * watching; \c configure can be used to modify some of the participation
118 * settings (e.g., bitrate cap); \c joinandconfigure combines the previous
119 * two requests in a single one (just for publishers); \c publish can be
120 * used to start sending media to broadcast to the other participants,
121 * while \c unpublish does the opposite; \c start allows you to start
122 * receiving media from a publisher you've subscribed to previously by
123 * means of a \c join , while \c pause pauses the delivery of the media;
124 * the \c switch request can be used to change the source of the media
125 * flowing over a specific PeerConnection (e.g., I was watching Alice,
126 * I want to watch Bob now) without having to create a new handle for
127 * that; \c finally, \c leave allows you to leave a video room for good
128 * (or, in the case of viewers, definitely closes a subscription).
129 *
130 * \c create can be used to create a new video room, and has to be
131 * formatted as follows:
132 *
133\verbatim
134{
135 "request" : "create",
136 "room" : <unique numeric ID, optional, chosen by plugin if missing>,
137 "permanent" : <true|false, whether the room should be saved in the config file, default=false>,
138 "description" : "<pretty name of the room, optional>",
139 "secret" : "<password required to edit/destroy the room, optional>",
140 "pin" : "<password required to join the room, optional>",
141 "is_private" : <true|false, whether the room should appear in a list request>,
142 "allowed" : [ array of string tokens users can use to join this room, optional],
143 ...
144}
145\endverbatim
146 *
147 * For the sake of brevity, not all of the available settings are listed
148 * here. You can refer to the name of the properties in the configuration
149 * file as a reference, as the ones used to programmatically create a new
150 * room are exactly the same.
151 *
152 * A successful creation procedure will result in a \c created response:
153 *
154\verbatim
155{
156 "videoroom" : "created",
157 "room" : <unique numeric ID>,
158 "permanent" : <true if saved to config file, false if not>
159}
160\endverbatim
161 *
162 * If you requested a permanent room but a \c false value is returned
163 * instead, good chances are that there are permission problems.
164 *
165 * An error instead (and the same applies to all other requests, so this
166 * won't be repeated) would provide both an error code and a more verbose
167 * description of the cause of the issue:
168 *
169\verbatim
170{
171 "videoroom" : "event",
172 "error_code" : <numeric ID, check Macros below>,
173 "error" : "<error description as a string>"
174}
175\endverbatim
176 *
177 * Notice that, in general, all users can create rooms. If you want to
178 * limit this functionality, you can configure an admin \c admin_key in
179 * the plugin settings. When configured, only "create" requests that
180 * include the correct \c admin_key value in an "admin_key" property
181 * will succeed, and will be rejected otherwise. Notice that you can
182 * optionally extend this functionality to RTP forwarding as well, in
183 * order to only allow trusted clients to use that feature.
184 *
185 * Once a room has been created, you can still edit some (but not all)
186 * of its properties using the \c edit request. This allows you to modify
187 * the room description, secret, pin and whether it's private or not: you
188 * won't be able to modify other more static properties, like the room ID,
189 * the sampling rate, the extensions-related stuff and so on. If you're
190 * interested in changing the ACL, instead, check the \c allowed message.
191 * An \c edit request has to be formatted as follows:
192 *
193\verbatim
194{
195 "request" : "edit",
196 "room" : <unique numeric ID of the room to edit>,
197 "secret" : "<room secret, mandatory if configured>",
198 "new_description" : "<new pretty name of the room, optional>",
199 "new_secret" : "<new password required to edit/destroy the room, optional>",
200 "new_pin" : "<new password required to join the room, optional>",
201 "new_is_private" : <true|false, whether the room should appear in a list request>,
202 "new_require_pvtid" : <true|false, whether the room should require private_id from subscribers>,
203 "new_bitrate" : <new bitrate cap to force on all publishers (except those with custom overrides)>,
204 "new_fir_freq" : <new period for regular PLI keyframe requests to publishers>,
205 "new_publishers" : <new cap on the number of concurrent active WebRTC publishers>,
206 "permanent" : <true|false, whether the room should be also removed from the config file, default=false>
207}
208\endverbatim
209 *
210 * A successful edit procedure will result in an \c edited response:
211 *
212\verbatim
213{
214 "videoroom" : "edited",
215 "room" : <unique numeric ID>
216}
217\endverbatim
218 *
219 * On the other hand, \c destroy can be used to destroy an existing video
220 * room, whether created dynamically or statically, and has to be
221 * formatted as follows:
222 *
223\verbatim
224{
225 "request" : "destroy",
226 "room" : <unique numeric ID of the room to destroy>,
227 "secret" : "<room secret, mandatory if configured>",
228 "permanent" : <true|false, whether the room should be also removed from the config file, default=false>
229}
230\endverbatim
231 *
232 * A successful destruction procedure will result in a \c destroyed response:
233 *
234\verbatim
235{
236 "videoroom" : "destroyed",
237 "room" : <unique numeric ID>
238}
239\endverbatim
240 *
241 * This will also result in a \c destroyed event being sent to all the
242 * participants in the video room, which will look like this:
243 *
244\verbatim
245{
246 "videoroom" : "destroyed",
247 "room" : <unique numeric ID of the destroyed room>
248}
249\endverbatim
250 *
251 * You can check whether a room exists using the \c exists request,
252 * which has to be formatted as follows:
253 *
254\verbatim
255{
256 "request" : "exists",
257 "room" : <unique numeric ID of the room to check>
258}
259\endverbatim
260 *
261 * A successful request will result in a \c success response:
262 *
263\verbatim
264{
265 "videoroom" : "success",
266 "room" : <unique numeric ID>,
267 "exists" : <true|false>
268}
269\endverbatim
270 *
271 * You can configure whether to check tokens or add/remove people who can join
272 * a room using the \c allowed request, which has to be formatted as follows:
273 *
274\verbatim
275{
276 "request" : "allowed",
277 "secret" : "<room secret, mandatory if configured>",
278 "action" : "enable|disable|add|remove",
279 "room" : <unique numeric ID of the room to update>,
280 "allowed" : [
281 // Array of strings (tokens users might pass in "join", only for add|remove)
282 ]
283}
284\endverbatim
285 *
286 * A successful request will result in a \c success response:
287 *
288\verbatim
289{
290 "videoroom" : "success",
291 "room" : <unique numeric ID>,
292 "allowed" : [
293 // Updated, complete, list of allowed tokens (only for enable|add|remove)
294 ]
295}
296\endverbatim
297 *
298 * If you're the administrator of a room (that is, you created it and have access
299 * to the secret) you can kick participants using the \c kick request. Notice
300 * that this only kicks the user out of the room, but does not prevent them from
301 * re-joining: to ban them, you need to first remove them from the list of
302 * authorized users (see \c allowed request) and then \c kick them. The \c kick
303 * request has to be formatted as follows:
304 *
305\verbatim
306{
307 "request" : "kick",
308 "secret" : "<room secret, mandatory if configured>",
309 "room" : <unique numeric ID of the room>,
310 "id" : <unique numeric ID of the participant to kick>
311}
312\endverbatim
313 *
314 * A successful request will result in a \c success response:
315 *
316\verbatim
317{
318 "videoroom" : "success",
319}
320\endverbatim
321 *
322 * To get a list of the available rooms (excluded those configured or
323 * created as private rooms) you can make use of the \c list request,
324 * which has to be formatted as follows:
325 *
326\verbatim
327{
328 "request" : "list"
329}
330\endverbatim
331 *
332 * A successful request will produce a list of rooms in a \c success response:
333 *
334\verbatim
335{
336 "videoroom" : "success",
337 "rooms" : [ // Array of room objects
338 { // Room #1
339 "room" : <unique numeric ID>,
340 "description" : "<Name of the room>",
341 "pin_required" : <true|false, whether a PIN is required to join this room>,
342 "max_publishers" : <how many publishers can actually publish via WebRTC at the same time>,
343 "bitrate" : <bitrate cap that should be forced (via REMB) on all publishers by default>,
344 "bitrate_cap" : <true|false, whether the above cap should act as a limit to dynamic bitrate changes by publishers>,
345 "fir_freq" : <how often a keyframe request is sent via PLI/FIR to active publishers>,
346 "audiocodec" : "<comma separated list of allowed audio codecs>",
347 "videocodec" : "<comma separated list of allowed video codecs>",
348 "record" : <true|false, whether the room is being recorded>,
349 "record_dir" : "<if recording, the path where the .mjr files are being saved>",
350 "num_participants" : <count of the participants (publishers, active or not; not subscribers)>
351 },
352 // Other rooms
353 ]
354}
355\endverbatim
356 *
357 * To get a list of the participants in a specific room, instead, you
358 * can make use of the \c listparticipants request, which has to be
359 * formatted as follows:
360 *
361\verbatim
362{
363 "request" : "listparticipants",
364 "room" : <unique numeric ID of the room>
365}
366\endverbatim
367 *
368 * A successful request will produce a list of participants in a
369 * \c participants response:
370 *
371\verbatim
372{
373 "videoroom" : "participants",
374 "room" : <unique numeric ID of the room>,
375 "participants" : [ // Array of participant objects
376 { // Participant #1
377 "id" : <unique numeric ID of the participant>,
378 "display" : "<display name of the participant, if any; optional>",
379 "talking" : <true|false, whether user is talking or not (only if audio levels are used)>,
380 "internal_audio_ssrc" : <audio SSRC used internally for this active publisher>,
381 "internal_video_ssrc" : <video SSRC used internally for this active publisher>
382 },
383 // Other participants
384 ]
385}
386\endverbatim
387 *
388 * This covers almost all the synchronous requests. All the asynchronous requests,
389 * plus a couple of additional synchronous requests we'll cover later, refer
390 * to participants instead, namely on how they can publish, subscribe, or
391 * more in general manage the media streams they may be sending or receiving.
392 *
393 * Considering the different nature of publishers and subscribers in the room,
394 * and more importantly how you establish PeerConnections in the respective
395 * cases, their API requests are addressed in separate subsections.
396 *
397 * \subsection vroompub VideoRoom Publishers
398 *
399 * In a VideoRoom, publishers are those participant handles that are able
400 * (although may choose not to, more on this later) publish media in the
401 * room, and as such become feeds that you can subscribe to.
402 *
403 * To specify a handle will be associated with a publisher, you must use
404 * the \c join request with \c ptype set to \c publisher (note that, as it
405 * will be explained later, you can also use \c joinandconfigure for the
406 * purpose). The exact syntax of the request is the following:
407 *
408\verbatim
409{
410 "request" : "join",
411 "ptype" : "publisher",
412 "room" : <unique ID of the room to join>,
413 "id" : <unique ID to register for the publisher; optional, will be chosen by the plugin if missing>,
414 "display" : "<display name for the publisher; optional>",
415 "token" : "<invitation token, in case the room has an ACL; optional>"
416}
417\endverbatim
418 *
419 * This will add the user to the list of participants in the room, although
420 * in a non-active role for the time being. Anyway, this participation
421 * allows the user to receive notifications about several aspects of the
422 * room on the related handle (including streams as they become available
423 * and go away). As such, it can be used even just as a way to get
424 * notifications in a room, without the need of ever actually publishing
425 * any stream at all (which explains why the "publisher" role may actually
426 * be a bit confusing in this context).
427 *
428 * A successful \c join will result in a \c joined event, which will contain
429 * a list of the currently active (as in publishing via WebRTC) publishers,
430 * and optionally a list of passive attendees (but only if the room was
431 * configured with \c notify_joining set to \c TRUE ):
432 *
433\verbatim
434{
435 "videoroom" : "joined",
436 "room" : <room ID>,
437 "description" : <description of the room, if available>,
438 "id" : <unique ID of the participant>,
439 "private_id" : <a different unique ID associated to the participant; meant to be private>,
440 "publishers" : [
441 {
442 "id" : <unique ID of active publisher #1>,
443 "display" : "<display name of active publisher #1, if any>",
444 "audio_codec" : "<audio codec used by active publisher #1, if any>",
445 "video_codec" : "<video codec used by active publisher #1, if any>",
446 "simulcast" : "<true if the publisher uses simulcast (VP8 and H.264 only)>",
447 "talking" : <true|false, whether the publisher is talking or not (only if audio levels are used)>,
448 },
449 // Other active publishers
450 ],
451 "attendees" : [ // Only present when notify_joining is set to TRUE for rooms
452 {
453 "id" : <unique ID of attendee #1>,
454 "display" : "<display name of attendee #1, if any>"
455 },
456 // Other attendees
457 ]
458}
459\endverbatim
460 *
461 * Notice that the publishers list will of course be empty if no one is
462 * currently active in the room. For what concerns the \c private_id
463 * property, it is meant to be used by the user when they create subscriptions,
464 * so that the plugin can associate subscriber handles (which are typically
465 * anonymous) to a specific participant; they're usually optional, unless
466 * required by the room configuration.
467 *
468 * As explained, with a simple \c join you're not an active publisher (there
469 * is no WebRTC PeerConnection yet), which means that by default your presence
470 * is not notified to other participants. In fact, the publish/subscribe nature
471 * of the plugin implies that by default only active publishers are notified,
472 * to allow participants to subscribe to existing feeds: notifying all joins/leaves,
473 * even those related to who will just lurk, may be overly verbose and chatty,
474 * especially in large rooms. Anyway, rooms can be configured to notify those
475 * as well, if the \c notify_joining property is set to true: in that case,
476 * regular joins will be notified too, in an event formatted like this:
477 *
478\verbatim
479{
480 "videoroom" : "event",
481 "room" : <room ID>,
482 "joining" : {
483 "id" : <unique ID of the new participant>,
484 "display" : "<display name of the new participant, if any>"
485 }
486}
487\endverbatim
488 *
489 * If you're interested in publishing media within a room, you can do that
490 * with a \c publish request. This request MUST be accompanied by a JSEP
491 * SDP offer to negotiate a new PeerConnection. The plugin will match it
492 * to the room configuration (e.g., to make sure the codecs you negotiated
493 * are allowed in the room), and will reply with a JSEP SDP answer to
494 * close the circle and complete the setup of the PeerConnection. As soon
495 * as the PeerConnection has been establisher, the publisher will become
496 * active, and a new active feed other participants can subscribe to.
497 *
498 * The syntax of a \c publish request is the following:
499 *
500\verbatim
501{
502 "request" : "publish",
503 "audio" : <true|false, depending on whether or not audio should be relayed; true by default>,
504 "video" : <true|false, depending on whether or not video should be relayed; true by default>,
505 "data" : <true|false, depending on whether or not data should be relayed; true by default>,
506 "audiocodec" : "<audio codec to prefer among the negotiated ones; optional>",
507 "videocodec" : "<video codec to prefer among the negotiated ones; optional>",
508 "bitrate" : <bitrate cap to return via REMB; optional, overrides the global room value if present>,
509 "record" : <true|false, whether this publisher should be recorded or not; optional>,
510 "filename" : "<if recording, the base path/file to use for the recording files; optional>",
511 "display" : "<new display name to use in the room; optional>"
512}
513\endverbatim
514 *
515 * As anticipated, since this is supposed to be accompanied by a JSEP SDP
516 * offer describing the publisher's media streams, the plugin will negotiate
517 * and prepare a matching JSEP SDP answer. If successful, a \c configured
518 * event will be sent back, formatted like this:
519 *
520\verbatim
521{
522 "videoroom" : "event",
523 "configured" : "ok"
524}
525\endverbatim
526 *
527 * This event will be accompanied by the prepared JSEP SDP answer.
528 *
529 * Notice that you can also use \c configure as a request instead of
530 * \c publish to start publishing. The two are functionally equivalent
531 * for publishing, but from a semantic perspective \c publish is the
532 * right message to send when publishing. The \c configure request, as
533 * it will be clearer later, can also be used to update some properties
534 * of the publisher session: in this case the \c publish request can NOT
535 * be used, as it can only be invoked to publish, and will fail if you're
536 * already publishing something.
537 *
538 * As an additional note, notice that you can also join and publish in
539 * a single request, which is useful in case you're not interested in
540 * first join as a passive attendee and only later publish something,
541 * but want to publish something right away. In this case you can use
542 * the \c joinandconfigure request, which as you can imagine combines
543 * the properties of both \c join and \c publish in a single request:
544 * the response to a \c joinandconfigure will be a \c joined event, and
545 * will again be accompanied by a JSEP SDP answer as usual.
546 *
547 * However you decided to publish something, as soon as the PeerConnection
548 * setup succeeds and the publisher becomes active, an event is sent to
549 * all the participants in the room with information on the new feed.
550 * The event must contain an array with a single element, and be formatted like this:
551 *
552\verbatim
553{
554 "videoroom" : "event",
555 "room" : <room ID>,
556 "publishers" : [
557 {
558 "id" : <unique ID of the new publisher>,
559 "display" : "<display name of the new publisher, if any>",
560 "audio_codec" : "<audio codec used the new publisher, if any>",
561 "video_codec" : "<video codec used by the new publisher, if any>",
562 "simulcast" : "<true if the publisher uses simulcast (VP8 and H.264 only)>",
563 "talking" : <true|false, whether the publisher is talking or not (only if audio levels are used)>,
564 }
565 ]
566}
567\endverbatim
568 *
569 * To stop publishing and tear down the related PeerConnection, you can
570 * use the \c unpublish request, which requires no arguments as the context
571 * is implicit:
572 *
573\verbatim
574{
575 "request" : "unpublish"
576}
577\endverbatim
578 *
579 * This will have the plugin tear down the PeerConnection, and remove the
580 * publisher from the list of active streams. If successful, the response
581 * will look like this:
582 *
583\verbatim
584{
585 "videoroom" : "event",
586 "unpublished" : "ok"
587}
588\endverbatim
589 *
590 * As soon as the PeerConnection is gone, all the other participants will
591 * also be notified about the fact that the stream is no longer available:
592 *
593\verbatim
594{
595 "videoroom" : "event",
596 "room" : <room ID>,
597 "unpublished" : <unique ID of the publisher who unpublished>
598}
599\endverbatim
600 *
601 * Notice that the same event will also be sent whenever the publisher
602 * feed disappears for reasons other than an explicit \c unpublish , e.g.,
603 * because the handle was closed or the user lost their connection.
604 * Besides, notice that you can publish and unpublish multiple times
605 * within the context of the same publisher handle.
606 *
607 * As anticipated above, you can use a request called \c configure to
608 * tweak some of the properties of an active publisher session. This
609 * request must be formatted as follows:
610 *
611\verbatim
612{
613 "request" : "configure",
614 "audio" : <true|false, depending on whether or not audio should be relayed; true by default>,
615 "video" : <true|false, depending on whether or not video should be relayed; true by default>,
616 "data" : <true|false, depending on whether or not data should be relayed; true by default>,
617 "bitrate" : <bitrate cap to return via REMB; optional, overrides the global room value if present (unless bitrate_cap is set)>,
618 "keyframe" : <true|false, whether we should send this publisher a keyframe request>,
619 "record" : <true|false, whether this publisher should be recorded or not; optional>,
620 "filename" : "<if recording, the base path/file to use for the recording files; optional>",
621 "display" : "<new display name to use in the room; optional>"
622}
623\endverbatim
624 *
625 * As you can see, it's basically the same properties as those listed for
626 * \c publish . This is why both requests can be used to start publishing,
627 * as even in that case you configure some of the settings. If successful,
628 * a \c configured event will be sent back as before, formatted like this:
629 *
630\verbatim
631{
632 "videoroom" : "event",
633 "configured" : "ok"
634}
635\endverbatim
636 *
637 * When configuring the room to request the ssrc-audio-level RTP extension,
638 * ad-hoc events might be sent to all publishers if \c audiolevel_event is
639 * set to true. These events will have the following format:
640 *
641\verbatim
642{
643 "videoroom" : <"talking"|"stopped-talking", whether the publisher started or stopped talking>,
644 "room" : <unique numeric ID of the room the publisher is in>,
645 "id" : <unique numeric ID of the publisher>,
646 "audio-level-dBov-avg" : <average value of audio level, 127=muted, 0='too loud'>
647}
648\endverbatim
649 *
650 * An interesting feature VideoRoom publisher can take advantage of is
651 * RTP forwarding. In fact, while the main purpose of this plugin is
652 * getting media from WebRTC sources (publishers) and relaying it to
653 * WebRTC destinations (subscribers), there are actually several use
654 * cases and scenarios for making this media available to external,
655 * notnecessarily WebRTC-compliant, components. These components may
656 * benefit from having access to the RTP media sent by a publisher, e.g.,
657 * for media processing, external recording, transcoding to other
658 * technologies via other applications, scalability purposes or
659 * whatever else makes sense in this context. This is made possible by
660 * a request called \c rtp_forward which, as the name suggests, simply
661 * forwards in real-time the media sent by a publisher via RTP (plain
662 * or encrypted) to a remote backend.
663 *
664 * You can add a new RTP forwarder for an existing publisher using the
665 * \c rtp_forward request, which has to be formatted as follows:
666 *
667\verbatim
668{
669 "request" : "rtp_forward",
670 "room" : <unique numeric ID of the room the publisher is in>,
671 "publisher_id" : <unique numeric ID of the publisher to relay externally>,
672 "host" : "<host address to forward the RTP and data packets to>",
673 "audio_port" : <port to forward the audio RTP packets to>,
674 "audio_ssrc" : <audio SSRC to use to use when streaming; optional>,
675 "audio_pt" : <audio payload type to use when streaming; optional>,
676 "audio_rtcp_port" : <port to contact to receive audio RTCP feedback from the recipient; optional, and currently unused for audio>,
677 "video_port" : <port to forward the video RTP packets to>,
678 "video_ssrc" : <video SSRC to use to use when streaming; optional>,
679 "video_pt" : <video payload type to use when streaming; optional>,
680 "video_rtcp_port" : <port to contact to receive video RTCP feedback from the recipient; optional>,
681 "video_port_2" : <if simulcasting, port to forward the video RTP packets from the second substream/layer to>,
682 "video_ssrc_2" : <if simulcasting, video SSRC to use to use the second substream/layer; optional>,
683 "video_pt_2" : <if simulcasting, video payload type to use the second substream/layer; optional>,
684 "video_port_3" : <if simulcasting, port to forward the video RTP packets from the third substream/layer to>,
685 "video_ssrc_3" : <if simulcasting, video SSRC to use to use the third substream/layer; optional>,
686 "video_pt_3" : <if simulcasting, video payload type to use the third substream/layer; optional>,
687 "data_port" : <port to forward the datachannel messages to>,
688 "srtp_suite" : <length of authentication tag (32 or 80); optional>,
689 "srtp_crypto" : "<key to use as crypto (base64 encoded key as in SDES); optional>"
690}
691\endverbatim
692 *
693 * Notice that, as explained above, in case you configured an \c admin_key
694 * property and extended it to RTP forwarding as well, you'll need to provide
695 * it in the request as well or it will be rejected as unauthorized. By
696 * default no limitation is posed on \c rtp_forward .
697 *
698 * A successful request will result in an \c rtp_forward response, containing
699 * the relevant info associated to the new forwarder(s):
700 *
701\verbatim
702{
703 "videoroom" : "rtp_forward",
704 "room" : <unique numeric ID, same as request>,
705 "publisher_id" : <unique numeric ID, same as request>,
706 "rtp_stream" : {
707 "host" : "<host this forwarder is streaming to, same as request>",
708 "audio" : <audio RTP port, same as request if configured>,
709 "audio_rtcp" : <audio RTCP port, same as request if configured>,
710 "audio_stream_id" : <unique numeric ID assigned to the audio RTP forwarder, if any>,
711 "video" : <video RTP port, same as request if configured>,
712 "video_rtcp" : <video RTCP port, same as request if configured>,
713 "video_stream_id" : <unique numeric ID assigned to the main video RTP forwarder, if any>,
714 "video_2" : <second video port, same as request if configured>,
715 "video_stream_id_2" : <unique numeric ID assigned to the second video RTP forwarder, if any>,
716 "video_3" : <third video port, same as request if configured>,
717 "video_stream_id_3" : <unique numeric ID assigned to the third video RTP forwarder, if any>,
718 "data" : <data port, same as request if configured>,
719 "data_stream_id" : <unique numeric ID assigned to datachannel messages forwarder, if any>
720 }
721}
722\endverbatim
723 *
724 * To stop a previously created RTP forwarder and stop it, you can use
725 * the \c stop_rtp_forward request, which has to be formatted as follows:
726 *
727\verbatim
728{
729 "request" : "stop_rtp_forward",
730 "room" : <unique numeric ID of the room the publisher is in>,
731 "publisher_id" : <unique numeric ID of the publisher to update>,
732 "stream_id" : <unique numeric ID of the RTP forwarder>
733}
734\endverbatim
735 *
736 * A successful request will result in a \c stop_rtp_forward response:
737 *
738\verbatim
739{
740 "videoroom" : "stop_rtp_forward",
741 "room" : <unique numeric ID, same as request>,
742 "publisher_id" : <unique numeric ID, same as request>,
743 "stream_id" : <unique numeric ID, same as request>
744}
745\endverbatim
746 *
747 * To get a list of all the forwarders in a specific room, instead, you
748 * can make use of the \c listforwarders request, which has to be
749 * formatted as follows:
750 *
751\verbatim
752{
753 "request" : "listforwarders",
754 "room" : <unique numeric ID of the room>,
755 "secret" : "<room secret; mandatory if configured>"
756}
757\endverbatim
758 *
759 * A successful request will produce a list of RTP forwarders in a
760 * \c forwarders response:
761 *
762\verbatim
763{
764 "videoroom" : "forwarders",
765 "room" : <unique numeric ID of the room>,
766 "rtp_forwarders" : [ // Array of publishers with RTP forwarders
767 { // Publisher #1
768 "publisher_id" : <unique numeric ID of publisher #1>,
769 "rtp_forwarders" : [ // Array of RTP forwarders
770 { // RTP forwarder #1
771 "audio_stream_id" : <unique numeric ID assigned to this audio RTP forwarder, if any>,
772 "video_stream_id" : <unique numeric ID assigned to this video RTP forwarder, if any>,
773 "data_stream_id" : <unique numeric ID assigned to this datachannel messages forwarder, if any>
774 "ip" : "<IP this forwarder is streaming to>",
775 "port" : <port this forwarder is streaming to>,
776 "rtcp_port" : <local port this forwarder is using to get RTCP feedback, if any>,
777 "ssrc" : <SSRC this forwarder is using, if any>,
778 "pt" : <payload type this forwarder is using, if any>,
779 "substream" : <video substream this video forwarder is relaying, if any>,
780 "srtp" : <true|false, whether the RTP stream is encrypted>
781 },
782 // Other forwarders for this publisher
783 ],
784 },
785 // Other publishers
786 ]
787}
788\endverbatim *
789 *
790 * To conclude, you can leave a room you previously joined as publisher
791 * using the \c leave request. This will also implicitly unpublish you
792 * if you were an active publisher in the room. The \c leave request
793 * looks like follows:
794 *
795\verbatim
796{
797 "request" : "leave"
798}
799\endverbatim
800 *
801 * If successful, the response will look like this:
802 *
803\verbatim
804{
805 "videoroom" : "event",
806 "leaving" : "ok"
807}
808\endverbatim
809 *
810 * Other participants will receive a different event depending on whether
811 * you were currently an active publisher ("unpublished") or simply
812 * lurking ("leaving"):
813 *
814\verbatim
815{
816 "videoroom" : "event",
817 "room" : <room ID>,
818 "leaving|unpublished" : <unique ID of the publisher who left>
819}
820\endverbatim
821 *
822 * \subsection vroomsub VideoRoom Subscribers
823 *
824 * In a VideoRoom, subscribers are NOT participants, but simply handles
825 * that will be used exclusively to receive media from a specific publisher
826 * in the room. Since they're not participants per se, they're basically
827 * streams that can be (and typically are) associated to publisher handles
828 * as the ones we introduced in the previous section, whether active or not.
829 * In fact, the typical use case is publishers being notified about new
830 * participants becoming active in the room, and as a result new subscriber
831 * sessions being created to receive their media streams; as soon as the
832 * publisher goes away, the subscriber handle is removed as well. As such,
833 * these subscriber sessions are dependent on feedback obtained by
834 * publishers, and can't exist on their own, unless you feed them the
835 * right info out of band.
836 *
837 * To specify a handle will be associated with a subscriber, you must use
838 * the \c join request with \c ptype set to \c subscriber and specify which
839 * feed to subscribe to. The exact syntax of the request is the following:
840 *
841\verbatim
842{
843 "request" : "join",
844 "ptype" : "subscriber",
845 "room" : <unique ID of the room to subscribe in>,
846 "feed" : <unique ID of the publisher to subscribe to; mandatory>,
847 "private_id" : <unique ID of the publisher that originated this request; optional, unless mandated by the room configuration>,
848 "close_pc" : <true|false, depending on whether or not the PeerConnection should be automatically closed when the publisher leaves; true by default>,
849 "audio" : <true|false, depending on whether or not audio should be relayed; true by default>,
850 "video" : <true|false, depending on whether or not video should be relayed; true by default>,
851 "data" : <true|false, depending on whether or not data should be relayed; true by default>,
852 "offer_audio" : <true|false; whether or not audio should be negotiated; true by default if the publisher has audio>,
853 "offer_video" : <true|false; whether or not video should be negotiated; true by default if the publisher has video>,
854 "offer_data" : <true|false; whether or not datachannels should be negotiated; true by default if the publisher has datachannels>,
855 "substream" : <substream to receive (0-2), in case simulcasting is enabled; optional>,
856 "temporal" : <temporal layers to receive (0-2), in case simulcasting is enabled; optional>,
857 "spatial_layer" : <spatial layer to receive (0-2), in case VP9-SVC is enabled; optional>,
858 "temporal_layer" : <temporal layers to receive (0-2), in case VP9-SVC is enabled; optional>
859}
860\endverbatim
861 *
862 * As you can see, it's just a matter of specifying the ID of the publisher to
863 * subscribe to and, if needed, your own \c private_id (if mandated by the room).
864 * The \c offer_audio , \c offer_video and \c offer_data are
865 * also particularly interesting, though, as they allow you to only subscribe
866 * to a subset of the mountpoint media. By default, in fact, this \c join
867 * request will result in the plugin preparing a new SDP offer trying to
868 * negotiate all the media streams made available by the publisher; in case
869 * the subscriber knows they don't support one of the mountpoint codecs, though
870 * (e.g., the video in the mountpoint is VP8, but they only support H.264),
871 * or are not interested in getting all the media (e.g., they're ok with
872 * just audio and not video, or don't have enough bandwidth for both),
873 * they can use those properties to shape the SDP offer to their needs.
874 * In case the publisher to subscribe to is simulcasting or doing VP9 SVC,
875 * you can choose in advance which substream you're interested in, e.g.,
876 * to only get the medium quality at best, instead of higher options if
877 * available. As we'll see later, this can be changed dynamically at any
878 * time using a subsequent \c configure request.
879 *
880 * As anticipated, if successful this request will generate a new JSEP SDP
881 * offer, which will accompany an \c attached event:
882 *
883\verbatim
884{
885 "videoroom" : "attached",
886 "room" : <room ID>,
887 "feed" : <publisher ID>,
888 "display" : "<the display name of the publisher, if any>"
889}
890\endverbatim
891 *
892 * At this stage, to complete the setup of the PeerConnection the subscriber is
893 * supposed to send a JSEP SDP answer back to the plugin. This is done
894 * by means of a \c start request, which in this case MUST be associated
895 * with a JSEP SDP answer but otherwise requires no arguments:
896 *
897\verbatim
898{
899 "request" : "start"
900}
901\endverbatim
902 *
903 * If successful this request returns a \c started event:
904 *
905\verbatim
906{
907 "videoroom" : "event",
908 "started" : "ok"
909}
910\endverbatim
911 *
912 * Once this is done, all that's needed is waiting for the WebRTC PeerConnection
913 * establishment to succeed. As soon as that happens, the Streaming plugin
914 * can start relaying media from the mountpoint the viewer subscribed to
915 * to the viewer themselves.
916 *
917 * Notice that the same exact steps we just went through (\c watch request,
918 * followed by JSEP offer by the plugin, followed by \c start request with
919 * JSEP answer by the viewer) is what you also use when renegotiations are
920 * needed, e.g., for the purpose of ICE restarts.
921 *
922 * As a subscriber, you can temporarily pause and resume the whole media delivery
923 * with a \c pause and, again, \c start request (in this case without any JSEP
924 * SDP answer attached). Neither expect other arguments, as the context
925 * is implicitly derived from the handle they're sent on:
926 *
927\verbatim
928{
929 "request" : "pause"
930}
931\endverbatim
932 *
933\verbatim
934{
935 "request" : "start"
936}
937\endverbatim
938 *
939 * Unsurprisingly, they just result in, respectively, \c paused and
940 * \c started events:
941 *
942\verbatim
943{
944 "videoroom" : "event",
945 "paused" : "ok"
946}
947\endverbatim
948 *
949\verbatim
950{
951 "videoroom" : "event",
952 "started" : "ok"
953}
954\endverbatim
955 *
956 * For more drill-down manipulations of a subscription, a \c configure
957 * request can be used instead. This request allows subscribers to dynamically
958 * change some properties associated to their media subscription, e.g.,
959 * in terms of what should and should not be sent at a specific time. A
960 * \c configure request must be formatted as follows:
961 *
962\verbatim
963{
964 "request" : "configure",
965 "audio" : <true|false, depending on whether audio should be relayed or not; optional>,
966 "video" : <true|false, depending on whether video should be relayed or not; optional>,
967 "data" : <true|false, depending on whether datachannel messages should be relayed or not; optional>,
968 "substream" : <substream to receive (0-2), in case simulcasting is enabled; optional>,
969 "temporal" : <temporal layers to receive (0-2), in case simulcasting is enabled; optional>,
970 "spatial_layer" : <spatial layer to receive (0-2), in case VP9-SVC is enabled; optional>,
971 "temporal_layer" : <temporal layers to receive (0-2), in case VP9-SVC is enabled; optional>
972}
973\endverbatim
974 *
975 * As you can see, the \c audio , \c video and \c data properties can be
976 * used as a media-level pause/resume functionality, whereas \c pause
977 * and \c start simply pause and resume all streams at the same time.
978 * The \c substream and \c temporal properties, instead, only make sense
979 * when the mountpoint is configured with video simulcasting support, and
980 * as such the viewer is interested in receiving a specific substream
981 * or temporal layer, rather than any other of the available ones.
982 * The \c spatial_layer and \c temporal_layer have exactly the same meaning,
983 * but within the context of VP9-SVC publishers, and will have no effect
984 * on subscriptions associated to regular publishers.
985 *
986 * Another interesting feature that subscribers can take advantage of is the
987 * so-called publisher "switching". Basically, when subscribed to a specific
988 * publisher and receiving media from them, you can at any time "switch"
989 * to a different publisher, and as such start receiving media from that
990 * other mountpoint instead. Think of it as changing channel on a TV: you
991 * keep on using the same PeerConnection, the plugin simply changes the
992 * source of the media transparently. Of course, while powerful and effective
993 * this request has some limitations. First of all, it switches both audio
994 * and video, meaning you can't just switch video and keep the audio from
995 * the previous publisher, for instance; besides, the two publishers
996 * must have the same media configuration, that is, use the same codecs,
997 * the same payload types, etc. In fact, since the same PeerConnection is
998 * used for this feature, switching to a publisher with a different
999 * configuration might result in media incompatible with the PeerConnection
1000 * setup being relayed to the subscriber, and as such in no audio/video being
1001 * played. That said, a \c switch request must be formatted like this:
1002 *
1003\verbatim
1004{
1005 "request" : "switch",
1006 "feed" : <unique ID of the new publisher to switch to; mandatory>,
1007 "audio" : <true|false, depending on whether audio should be relayed or not; optional>,
1008 "video" : <true|false, depending on whether video should be relayed or not; optional>,
1009 "data" : <true|false, depending on whether datachannel messages should be relayed or not; optional>
1010}
1011\endverbatim
1012 *
1013 * If successful, you'll be unsubscribed from the previous publisher,
1014 * and subscribed to the new publisher instead. The event to confirm
1015 * the switch was successful will look like this:
1016 *
1017\verbatim
1018{
1019 "videoroom" : "event",
1020 "switched" : "ok",
1021 "room" : <room ID>,
1022 "id" : <unique ID of the new publisher>
1023}
1024\endverbatim
1025 *
1026 * Finally, to stop the subscription to the mountpoint and tear down the
1027 * related PeerConnection, you can use the \c leave request. Since context
1028 * is implicit, no other argument is required:
1029 *
1030\verbatim
1031{
1032 "request" : "leave"
1033}
1034\endverbatim
1035 *
1036 * If successful, the plugin will attempt to tear down the PeerConnection,
1037 * and will send back a \c left event:
1038 *
1039\verbatim
1040{
1041 "videoroom" : "event",
1042 "left" : "ok",
1043}
1044\endverbatim
1045 */
1046
1047#include "plugin.h"
1048
1049#include <jansson.h>
1050
1051#include "../debug.h"
1052#include "../apierror.h"
1053#include "../config.h"
1054#include "../mutex.h"
1055#include "../rtp.h"
1056#include "../rtpsrtp.h"
1057#include "../rtcp.h"
1058#include "../record.h"
1059#include "../sdp-utils.h"
1060#include "../utils.h"
1061#include <sys/types.h>
1062#include <sys/socket.h>
1063
1064
1065/* Plugin information */
1066#define JANUS_VIDEOROOM_VERSION 9
1067#define JANUS_VIDEOROOM_VERSION_STRING "0.0.9"
1068#define JANUS_VIDEOROOM_DESCRIPTION "This is a plugin implementing a videoconferencing SFU (Selective Forwarding Unit) for Janus, that is an audio/video router."
1069#define JANUS_VIDEOROOM_NAME "JANUS VideoRoom plugin"
1070#define JANUS_VIDEOROOM_AUTHOR "Meetecho s.r.l."
1071#define JANUS_VIDEOROOM_PACKAGE "janus.plugin.videoroom"
1072
1073/* Plugin methods */
1074janus_plugin *create(void);
1075int janus_videoroom_init(janus_callbacks *callback, const char *config_path);
1076void janus_videoroom_destroy(void);
1077int janus_videoroom_get_api_compatibility(void);
1078int janus_videoroom_get_version(void);
1079const char *janus_videoroom_get_version_string(void);
1080const char *janus_videoroom_get_description(void);
1081const char *janus_videoroom_get_name(void);
1082const char *janus_videoroom_get_author(void);
1083const char *janus_videoroom_get_package(void);
1084void janus_videoroom_create_session(janus_plugin_session *handle, int *error);
1085struct janus_plugin_result *janus_videoroom_handle_message(janus_plugin_session *handle, char *transaction, json_t *message, json_t *jsep);
1086json_t *janus_videoroom_handle_admin_message(json_t *message);
1087void janus_videoroom_setup_media(janus_plugin_session *handle);
1088void janus_videoroom_incoming_rtp(janus_plugin_session *handle, int video, char *buf, int len);
1089void janus_videoroom_incoming_rtcp(janus_plugin_session *handle, int video, char *buf, int len);
1090void janus_videoroom_incoming_data(janus_plugin_session *handle, char *label, char *buf, int len);
1091void janus_videoroom_slow_link(janus_plugin_session *handle, int uplink, int video);
1092void janus_videoroom_hangup_media(janus_plugin_session *handle);
1093void janus_videoroom_destroy_session(janus_plugin_session *handle, int *error);
1094json_t *janus_videoroom_query_session(janus_plugin_session *handle);
1095
1096/* Plugin setup */
1097static janus_plugin janus_videoroom_plugin =
1098 JANUS_PLUGIN_INIT (
1099 .init = janus_videoroom_init,
1100 .destroy = janus_videoroom_destroy,
1101
1102 .get_api_compatibility = janus_videoroom_get_api_compatibility,
1103 .get_version = janus_videoroom_get_version,
1104 .get_version_string = janus_videoroom_get_version_string,
1105 .get_description = janus_videoroom_get_description,
1106 .get_name = janus_videoroom_get_name,
1107 .get_author = janus_videoroom_get_author,
1108 .get_package = janus_videoroom_get_package,
1109
1110 .create_session = janus_videoroom_create_session,
1111 .handle_message = janus_videoroom_handle_message,
1112 .handle_admin_message = janus_videoroom_handle_admin_message,
1113 .setup_media = janus_videoroom_setup_media,
1114 .incoming_rtp = janus_videoroom_incoming_rtp,
1115 .incoming_rtcp = janus_videoroom_incoming_rtcp,
1116 .incoming_data = janus_videoroom_incoming_data,
1117 .slow_link = janus_videoroom_slow_link,
1118 .hangup_media = janus_videoroom_hangup_media,
1119 .destroy_session = janus_videoroom_destroy_session,
1120 .query_session = janus_videoroom_query_session,
1121 );
1122
1123/* Plugin creator */
1124janus_plugin *create(void) {
1125 JANUS_LOG(LOG_VERB, "%s created!\n", JANUS_VIDEOROOM_NAME);
1126 return &janus_videoroom_plugin;
1127}
1128
1129/* Parameter validation */
1130static struct janus_json_parameter request_parameters[] = {
1131 {"request", JSON_STRING, JANUS_JSON_PARAM_REQUIRED}
1132};
1133static struct janus_json_parameter adminkey_parameters[] = {
1134 {"admin_key", JSON_STRING, JANUS_JSON_PARAM_REQUIRED}
1135};
1136static struct janus_json_parameter create_parameters[] = {
1137 {"room", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
1138 {"description", JSON_STRING, 0},
1139 {"is_private", JANUS_JSON_BOOL, 0},
1140 {"allowed", JSON_ARRAY, 0},
1141 {"secret", JSON_STRING, 0},
1142 {"pin", JSON_STRING, 0},
1143 {"require_pvtid", JANUS_JSON_BOOL, 0},
1144 {"bitrate", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
1145 {"bitrate_cap", JANUS_JSON_BOOL, 0},
1146 {"fir_freq", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
1147 {"publishers", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
1148 {"audiocodec", JSON_STRING, 0},
1149 {"videocodec", JSON_STRING, 0},
1150 {"opus_fec", JANUS_JSON_BOOL, 0},
1151 {"video_svc", JANUS_JSON_BOOL, 0},
1152 {"audiolevel_ext", JANUS_JSON_BOOL, 0},
1153 {"audiolevel_event", JANUS_JSON_BOOL, 0},
1154 {"audio_active_packets", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
1155 {"audio_level_average", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
1156 {"videoorient_ext", JANUS_JSON_BOOL, 0},
1157 {"playoutdelay_ext", JANUS_JSON_BOOL, 0},
1158 {"transport_wide_cc_ext", JANUS_JSON_BOOL, 0},
1159 {"record", JANUS_JSON_BOOL, 0},
1160 {"rec_dir", JSON_STRING, 0},
1161 {"permanent", JANUS_JSON_BOOL, 0},
1162 {"notify_joining", JANUS_JSON_BOOL, 0},
1163};
1164static struct janus_json_parameter edit_parameters[] = {
1165 {"room", JSON_INTEGER, JANUS_JSON_PARAM_REQUIRED | JANUS_JSON_PARAM_POSITIVE},
1166 {"secret", JSON_STRING, 0},
1167 {"new_description", JSON_STRING, 0},
1168 {"new_is_private", JANUS_JSON_BOOL, 0},
1169 {"new_secret", JSON_STRING, 0},
1170 {"new_pin", JSON_STRING, 0},
1171 {"new_require_pvtid", JANUS_JSON_BOOL, 0},
1172 {"new_bitrate", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
1173 {"new_fir_freq", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
1174 {"new_publishers", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
1175 {"permanent", JANUS_JSON_BOOL, 0}
1176};
1177static struct janus_json_parameter room_parameters[] = {
1178 {"room", JSON_INTEGER, JANUS_JSON_PARAM_REQUIRED | JANUS_JSON_PARAM_POSITIVE}
1179};
1180static struct janus_json_parameter destroy_parameters[] = {
1181 {"room", JSON_INTEGER, JANUS_JSON_PARAM_REQUIRED | JANUS_JSON_PARAM_POSITIVE},
1182 {"permanent", JANUS_JSON_BOOL, 0}
1183};
1184static struct janus_json_parameter allowed_parameters[] = {
1185 {"room", JSON_INTEGER, JANUS_JSON_PARAM_REQUIRED | JANUS_JSON_PARAM_POSITIVE},
1186 {"secret", JSON_STRING, 0},
1187 {"action", JSON_STRING, JANUS_JSON_PARAM_REQUIRED},
1188 {"allowed", JSON_ARRAY, 0}
1189};
1190static struct janus_json_parameter kick_parameters[] = {
1191 {"room", JSON_INTEGER, JANUS_JSON_PARAM_REQUIRED | JANUS_JSON_PARAM_POSITIVE},
1192 {"secret", JSON_STRING, 0},
1193 {"id", JSON_INTEGER, JANUS_JSON_PARAM_REQUIRED | JANUS_JSON_PARAM_POSITIVE}
1194};
1195static struct janus_json_parameter join_parameters[] = {
1196 {"room", JSON_INTEGER, JANUS_JSON_PARAM_REQUIRED | JANUS_JSON_PARAM_POSITIVE},
1197 {"ptype", JSON_STRING, JANUS_JSON_PARAM_REQUIRED},
1198 {"audio", JANUS_JSON_BOOL, 0},
1199 {"video", JANUS_JSON_BOOL, 0},
1200 {"data", JANUS_JSON_BOOL, 0},
1201 {"bitrate", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
1202 {"record", JANUS_JSON_BOOL, 0},
1203 {"filename", JSON_STRING, 0},
1204 {"token", JSON_STRING, 0}
1205};
1206static struct janus_json_parameter publish_parameters[] = {
1207 {"audio", JANUS_JSON_BOOL, 0},
1208 {"audiocodec", JSON_STRING, 0},
1209 {"video", JANUS_JSON_BOOL, 0},
1210 {"videocodec", JSON_STRING, 0},
1211 {"data", JANUS_JSON_BOOL, 0},
1212 {"bitrate", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
1213 {"keyframe", JANUS_JSON_BOOL, 0},
1214 {"record", JANUS_JSON_BOOL, 0},
1215 {"filename", JSON_STRING, 0},
1216 {"display", JSON_STRING, 0},
1217 /* The following are just to force a renegotiation and/or an ICE restart */
1218 {"update", JANUS_JSON_BOOL, 0},
1219 {"restart", JANUS_JSON_BOOL, 0}
1220};
1221static struct janus_json_parameter rtp_forward_parameters[] = {
1222 {"room", JSON_INTEGER, JANUS_JSON_PARAM_REQUIRED | JANUS_JSON_PARAM_POSITIVE},
1223 {"publisher_id", JSON_INTEGER, JANUS_JSON_PARAM_REQUIRED | JANUS_JSON_PARAM_POSITIVE},
1224 {"video_port", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
1225 {"video_rtcp_port", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
1226 {"video_ssrc", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
1227 {"video_pt", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
1228 {"video_port_2", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
1229 {"video_ssrc_2", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
1230 {"video_pt_2", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
1231 {"video_port_3", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
1232 {"video_ssrc_3", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
1233 {"video_pt_3", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
1234 {"audio_port", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
1235 {"audio_rtcp_port", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
1236 {"audio_ssrc", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
1237 {"audio_pt", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
1238 {"data_port", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
1239 {"host", JSON_STRING, JANUS_JSON_PARAM_REQUIRED},
1240 {"simulcast", JANUS_JSON_BOOL, 0},
1241 {"srtp_suite", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
1242 {"srtp_crypto", JSON_STRING, 0}
1243};
1244static struct janus_json_parameter stop_rtp_forward_parameters[] = {
1245 {"room", JSON_INTEGER, JANUS_JSON_PARAM_REQUIRED | JANUS_JSON_PARAM_POSITIVE},
1246 {"publisher_id", JSON_INTEGER, JANUS_JSON_PARAM_REQUIRED | JANUS_JSON_PARAM_POSITIVE},
1247 {"stream_id", JSON_INTEGER, JANUS_JSON_PARAM_REQUIRED | JANUS_JSON_PARAM_POSITIVE}
1248};
1249static struct janus_json_parameter publisher_parameters[] = {
1250 {"id", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
1251 {"display", JSON_STRING, 0}
1252};
1253static struct janus_json_parameter configure_parameters[] = {
1254 {"audio", JANUS_JSON_BOOL, 0},
1255 {"video", JANUS_JSON_BOOL, 0},
1256 {"data", JANUS_JSON_BOOL, 0},
1257 /* For VP8 (or H.264) simulcast */
1258 {"substream", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
1259 {"temporal", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
1260 /* For VP9 SVC */
1261 {"spatial_layer", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
1262 {"temporal_layer", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
1263 /* The following is to handle a renegotiation */
1264 {"update", JANUS_JSON_BOOL, 0},
1265};
1266static struct janus_json_parameter subscriber_parameters[] = {
1267 {"feed", JSON_INTEGER, JANUS_JSON_PARAM_REQUIRED | JANUS_JSON_PARAM_POSITIVE},
1268 {"private_id", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
1269 {"close_pc", JANUS_JSON_BOOL, 0},
1270 {"audio", JANUS_JSON_BOOL, 0},
1271 {"video", JANUS_JSON_BOOL, 0},
1272 {"data", JANUS_JSON_BOOL, 0},
1273 {"offer_audio", JANUS_JSON_BOOL, 0},
1274 {"offer_video", JANUS_JSON_BOOL, 0},
1275 {"offer_data", JANUS_JSON_BOOL, 0},
1276 /* For VP8 (or H.264) simulcast */
1277 {"substream", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
1278 {"temporal", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
1279 /* For VP9 SVC */
1280 {"spatial_layer", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
1281 {"temporal_layer", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
1282};
1283
1284/* Static configuration instance */
1285static janus_config *config = NULL;
1286static const char *config_folder = NULL;
1287static janus_mutex config_mutex = JANUS_MUTEX_INITIALIZER;
1288
1289/* Useful stuff */
1290static volatile gint initialized = 0, stopping = 0;
1291static gboolean notify_events = TRUE;
1292static janus_callbacks *gateway = NULL;
1293static GThread *handler_thread;
1294static void *janus_videoroom_handler(void *data);
1295static void janus_videoroom_relay_rtp_packet(gpointer data, gpointer user_data);
1296static void janus_videoroom_relay_data_packet(gpointer data, gpointer user_data);
1297static void janus_videoroom_hangup_media_internal(janus_plugin_session *handle);
1298
1299typedef enum janus_videoroom_p_type {
1300 janus_videoroom_p_type_none = 0,
1301 janus_videoroom_p_type_subscriber, /* Generic subscriber */
1302 janus_videoroom_p_type_publisher, /* Participant (for receiving events) and optionally publisher */
1303} janus_videoroom_p_type;
1304
1305typedef struct janus_videoroom_message {
1306 janus_plugin_session *handle;
1307 char *transaction;
1308 json_t *message;
1309 json_t *jsep;
1310} janus_videoroom_message;
1311static GAsyncQueue *messages = NULL;
1312static janus_videoroom_message exit_message;
1313
1314
1315typedef struct janus_videoroom {
1316 guint64 room_id; /* Unique room ID */
1317 gchar *room_name; /* Room description */
1318 gchar *room_secret; /* Secret needed to manipulate (e.g., destroy) this room */
1319 gchar *room_pin; /* Password needed to join this room, if any */
1320 gboolean is_private; /* Whether this room is 'private' (as in hidden) or not */
1321 gboolean require_pvtid; /* Whether subscriptions in this room require a private_id */
1322 int max_publishers; /* Maximum number of concurrent publishers */
1323 uint32_t bitrate; /* Global bitrate limit */
1324 gboolean bitrate_cap; /* Whether the above limit is insormountable */
1325 uint16_t fir_freq; /* Regular FIR frequency (0=disabled) */
1326 janus_audiocodec acodec[3]; /* Audio codec(s) to force on publishers */
1327 janus_videocodec vcodec[3]; /* Video codec(s) to force on publishers */
1328 gboolean do_opusfec; /* Whether inband FEC must be negotiated (note: only available for Opus) */
1329 gboolean do_svc; /* Whether SVC must be done for video (note: only available for VP9 right now) */
1330 gboolean audiolevel_ext; /* Whether the ssrc-audio-level extension must be negotiated or not for new publishers */
1331 gboolean audiolevel_event; /* Whether to emit event to other users about audiolevel */
1332 int audio_active_packets; /* Amount of packets with audio level for checkup */
1333 int audio_level_average; /* Average audio level */
1334 gboolean videoorient_ext; /* Whether the video-orientation extension must be negotiated or not for new publishers */
1335 gboolean playoutdelay_ext; /* Whether the playout-delay extension must be negotiated or not for new publishers */
1336 gboolean transport_wide_cc_ext; /* Whether the transport wide cc extension must be negotiated or not for new publishers */
1337 gboolean record; /* Whether the feeds from publishers in this room should be recorded */
1338 char *rec_dir; /* Where to save the recordings of this room, if enabled */
1339 GHashTable *participants; /* Map of potential publishers (we get subscribers from them) */
1340 GHashTable *private_ids; /* Map of existing private IDs */
1341 volatile gint destroyed; /* Whether this room has been destroyed */
1342 gboolean check_allowed; /* Whether to check tokens when participants join (see below) */
1343 GHashTable *allowed; /* Map of participants (as tokens) allowed to join */
1344 gboolean notify_joining; /* Whether an event is sent to notify all participants if a new participant joins the room */
1345 janus_mutex mutex; /* Mutex to lock this room instance */
1346 janus_refcount ref; /* Reference counter for this room */
1347} janus_videoroom;
1348static GHashTable *rooms;
1349static janus_mutex rooms_mutex = JANUS_MUTEX_INITIALIZER;
1350static char *admin_key = NULL;
1351static gboolean lock_rtpfwd = FALSE;
1352
1353typedef struct janus_videoroom_session {
1354 janus_plugin_session *handle;
1355 gint64 sdp_sessid;
1356 gint64 sdp_version;
1357 janus_videoroom_p_type participant_type;
1358 gpointer participant;
1359 gboolean started;
1360 gboolean stopping;
1361 volatile gint hangingup;
1362 volatile gint destroyed;
1363 janus_mutex mutex;
1364 janus_refcount ref;
1365} janus_videoroom_session;
1366static GHashTable *sessions;
1367static janus_mutex sessions_mutex = JANUS_MUTEX_INITIALIZER;
1368
1369/* A host whose ports gets streamed RTP packets of the corresponding type */
1370typedef struct janus_videoroom_srtp_context janus_videoroom_srtp_context;
1371typedef struct janus_videoroom_rtp_forwarder {
1372 void *source;
1373 gboolean is_video;
1374 gboolean is_data;
1375 uint32_t ssrc;
1376 int payload_type;
1377 int substream;
1378 struct sockaddr_in serv_addr;
1379 /* Only needed for RTCP */
1380 int rtcp_fd;
1381 uint16_t local_rtcp_port, remote_rtcp_port;
1382 GSource *rtcp_recv;
1383 /* Only needed when forwarding simulcasted streams to a single endpoint */
1384 gboolean simulcast;
1385 janus_rtp_switching_context context;
1386 janus_rtp_simulcasting_context sim_context;
1387 /* Only needed for SRTP forwarders */
1388 gboolean is_srtp;
1389 janus_videoroom_srtp_context *srtp_ctx;
1390 /* Reference */
1391 volatile gint destroyed;
1392 janus_refcount ref;
1393} janus_videoroom_rtp_forwarder;
1394static void janus_videoroom_rtp_forwarder_destroy(janus_videoroom_rtp_forwarder *forward);
1395static void janus_videoroom_rtp_forwarder_free(const janus_refcount *f_ref);
1396/* SRTP encryption may be needed, and potentially shared */
1397struct janus_videoroom_srtp_context {
1398 GHashTable *contexts;
1399 char *id;
1400 srtp_t ctx;
1401 srtp_policy_t policy;
1402 char sbuf[1500];
1403 int slen;
1404 /* Keep track of how many forwarders are using this context */
1405 uint8_t count;
1406};
1407static void janus_videoroom_srtp_context_free(gpointer data);
1408/* RTCP support in RTP forwarders */
1409typedef struct janus_videoroom_rtcp_receiver {
1410 GSource parent;
1411 janus_videoroom_rtp_forwarder *forward;
1412 GDestroyNotify destroy;
1413} janus_videoroom_rtcp_receiver;
1414static void janus_videoroom_rtp_forwarder_rtcp_receive(janus_videoroom_rtp_forwarder *forward);
1415static gboolean janus_videoroom_rtp_forwarder_rtcp_prepare(GSource *source, gint *timeout) {
1416 *timeout = -1;
1417 return FALSE;
1418}
1419static gboolean janus_videoroom_rtp_forwarder_rtcp_dispatch(GSource *source, GSourceFunc callback, gpointer user_data) {
1420 janus_videoroom_rtcp_receiver *r = (janus_videoroom_rtcp_receiver *)source;
1421 /* Receive the packet */
1422 if(r)
1423 janus_videoroom_rtp_forwarder_rtcp_receive(r->forward);
1424 return G_SOURCE_CONTINUE;
1425}
1426static void janus_videoroom_rtp_forwarder_rtcp_finalize(GSource *source) {
1427 janus_videoroom_rtcp_receiver *r = (janus_videoroom_rtcp_receiver *)source;
1428 /* Remove the reference to the forwarder */
1429 if(r && r->forward)
1430 janus_refcount_decrease(&r->forward->ref);
1431}
1432static GSourceFuncs janus_videoroom_rtp_forwarder_rtcp_funcs = {
1433 janus_videoroom_rtp_forwarder_rtcp_prepare,
1434 NULL,
1435 janus_videoroom_rtp_forwarder_rtcp_dispatch,
1436 janus_videoroom_rtp_forwarder_rtcp_finalize,
1437 NULL, NULL
1438};
1439static GMainContext *rtcpfwd_ctx = NULL;
1440static GMainLoop *rtcpfwd_loop = NULL;
1441static GThread *rtcpfwd_thread = NULL;
1442static void *janus_videoroom_rtp_forwarder_rtcp_thread(void *data);
1443
1444typedef struct janus_videoroom_publisher {
1445 janus_videoroom_session *session;
1446 janus_videoroom *room; /* Room */
1447 guint64 room_id; /* Unique room ID */
1448 guint64 user_id; /* Unique ID in the room */
1449 guint32 pvt_id; /* This is sent to the publisher for mapping purposes, but shouldn't be shared with others */
1450 gchar *display; /* Display name (just for fun) */
1451 gchar *sdp; /* The SDP this publisher negotiated, if any */
1452 gboolean audio, video, data; /* Whether audio, video and/or data is going to be sent by this publisher */
1453 janus_audiocodec acodec; /* Audio codec this publisher is using */
1454 janus_videocodec vcodec; /* Video codec this publisher is using */
1455 guint32 audio_pt; /* Audio payload type (Opus) */
1456 guint32 video_pt; /* Video payload type (depends on room configuration) */
1457 guint32 audio_ssrc; /* Audio SSRC of this publisher */
1458 guint32 video_ssrc; /* Video SSRC of this publisher */
1459 gboolean do_opusfec; /* Whether this publisher is sending inband Opus FEC */
1460 uint32_t ssrc[3]; /* Only needed in case VP8 (or H.264) simulcasting is involved */
1461 char *rid[3]; /* Only needed if simulcasting is rid-based */
1462 int rid_extmap_id; /* rid extmap ID */
1463 int framemarking_ext_id; /* Frame marking extmap ID */
1464 guint8 audio_level_extmap_id; /* Audio level extmap ID */
1465 guint8 video_orient_extmap_id; /* Video orientation extmap ID */
1466 guint8 playout_delay_extmap_id; /* Playout delay extmap ID */
1467 gboolean audio_active;
1468 gboolean video_active;
1469 int audio_dBov_level; /* Value in dBov of the audio level (last value from extension) */
1470 int audio_active_packets; /* Participant's number of audio packets to accumulate */
1471 int audio_dBov_sum; /* Participant's accumulated dBov value for audio level*/
1472 gboolean talking; /* Whether this participant is currently talking (uses audio levels extension) */
1473 gboolean data_active;
1474 gboolean firefox; /* We send Firefox users a different kind of FIR */
1475 uint32_t bitrate;
1476 gint64 remb_startup;/* Incremental changes on REMB to reach the target at startup */
1477 gint64 remb_latest; /* Time of latest sent REMB (to avoid flooding) */
1478 gint64 fir_latest; /* Time of latest sent FIR (to avoid flooding) */
1479 gint fir_seq; /* FIR sequence number */
1480 gboolean recording_active; /* Whether this publisher has to be recorded or not */
1481 gchar *recording_base; /* Base name for the recording (e.g., /path/to/filename, will generate /path/to/filename-audio.mjr and/or /path/to/filename-video.mjr */
1482 janus_recorder *arc; /* The Janus recorder instance for this publisher's audio, if enabled */
1483 janus_recorder *vrc; /* The Janus recorder instance for this user's video, if enabled */
1484 janus_recorder *drc; /* The Janus recorder instance for this publisher's data, if enabled */
1485 janus_rtp_switching_context rec_ctx;
1486 janus_rtp_simulcasting_context rec_simctx;
1487 janus_mutex rec_mutex; /* Mutex to protect the recorders from race conditions */
1488 GSList *subscribers; /* Subscriptions to this publisher (who's watching this publisher) */
1489 GSList *subscriptions; /* Subscriptions this publisher has created (who this publisher is watching) */
1490 janus_mutex subscribers_mutex;
1491 GHashTable *rtp_forwarders;
1492 GHashTable *srtp_contexts;
1493 janus_mutex rtp_forwarders_mutex;
1494 int udp_sock; /* The udp socket on which to forward rtp packets */
1495 gboolean kicked; /* Whether this participant has been kicked */
1496 volatile gint destroyed;
1497 janus_refcount ref;
1498} janus_videoroom_publisher;
1499static guint32 janus_videoroom_rtp_forwarder_add_helper(janus_videoroom_publisher *p,
1500 const gchar *host, int port, int rtcp_port, int pt, uint32_t ssrc,
1501 gboolean simulcast, int srtp_suite, const char *srtp_crypto,
1502 int substream, gboolean is_video, gboolean is_data);
1503
1504typedef struct janus_videoroom_subscriber {
1505 janus_videoroom_session *session;
1506 janus_videoroom *room; /* Room */
1507 guint64 room_id; /* Unique room ID */
1508 janus_videoroom_publisher *feed; /* Participant this subscriber is subscribed to */
1509 gboolean close_pc; /* Whether we should automatically close the PeerConnection when the publisher goes away */
1510 guint32 pvt_id; /* Private ID of the participant that is subscribing (if available/provided) */
1511 janus_sdp *sdp; /* Offer we sent this listener (may be updated within renegotiations) */
1512 janus_rtp_switching_context context; /* Needed in case there are publisher switches on this subscriber */
1513 janus_rtp_simulcasting_context sim_context;
1514 janus_vp8_simulcast_context vp8_context;
1515 gboolean audio, video, data; /* Whether audio, video and/or data must be sent to this subscriber */
1516 /* As above, but can't change dynamically (says whether something was negotiated at all in SDP) */
1517 gboolean audio_offered, video_offered, data_offered;
1518 gboolean paused;
1519 gboolean kicked; /* Whether this subscription belongs to a participant that has been kicked */
1520 /* The following are only relevant if we're doing VP9 SVC, and are not to be confused with plain
1521 * simulcast, which has similar info (substream/templayer) but in a completely different context */
1522 int spatial_layer, target_spatial_layer;
1523 int temporal_layer, target_temporal_layer;
1524 volatile gint destroyed;
1525 janus_refcount ref;
1526} janus_videoroom_subscriber;
1527
1528typedef struct janus_videoroom_rtp_relay_packet {
1529 janus_rtp_header *data;
1530 gint length;
1531 gboolean is_video;
1532 uint32_t ssrc[3];
1533 uint32_t timestamp;
1534 uint16_t seq_number;
1535 /* The following are only relevant if we're doing VP9 SVC*/
1536 gboolean svc;
1537 int spatial_layer;
1538 int temporal_layer;
1539 uint8_t pbit, dbit, ubit, bbit, ebit;
1540} janus_videoroom_rtp_relay_packet;
1541
1542
1543/* Freeing stuff */
1544static void janus_videoroom_subscriber_destroy(janus_videoroom_subscriber *s) {
1545 if(s && g_atomic_int_compare_and_exchange(&s->destroyed, 0, 1))
1546 janus_refcount_decrease(&s->ref);
1547}
1548
1549static void janus_videoroom_subscriber_free(const janus_refcount *s_ref) {
1550 janus_videoroom_subscriber *s = janus_refcount_containerof(s_ref, janus_videoroom_subscriber, ref);
1551 /* This subscriber can be destroyed, free all the resources */
1552 janus_sdp_destroy(s->sdp);
1553 g_free(s);
1554}
1555
1556static void janus_videoroom_publisher_dereference(janus_videoroom_publisher *p) {
1557 /* This is used by g_pointer_clear and g_hash_table_new_full so that NULL is only possible if that was inserted into the hash table. */
1558 janus_refcount_decrease(&p->ref);
1559}
1560
1561static void janus_videoroom_publisher_dereference_by_subscriber(janus_videoroom_publisher *p) {
1562 /* This is used by g_pointer_clear and g_hash_table_new_full so that NULL is only possible if that was inserted into the hash table. */
1563 janus_refcount_decrease(&p->session->ref);
1564 janus_refcount_decrease(&p->ref);
1565}
1566
1567static void janus_videoroom_publisher_dereference_nodebug(janus_videoroom_publisher *p) {
1568 janus_refcount_decrease_nodebug(&p->ref);
1569}
1570
1571static void janus_videoroom_publisher_destroy(janus_videoroom_publisher *p) {
1572 if(p && g_atomic_int_compare_and_exchange(&p->destroyed, 0, 1))
1573 janus_refcount_decrease(&p->ref);
1574}
1575
1576static void janus_videoroom_publisher_free(const janus_refcount *p_ref) {
1577 janus_videoroom_publisher *p = janus_refcount_containerof(p_ref, janus_videoroom_publisher, ref);
1578 g_free(p->display);
1579 p->display = NULL;
1580 g_free(p->sdp);
1581 p->sdp = NULL;
1582 g_free(p->recording_base);
1583 p->recording_base = NULL;
1584 janus_recorder_destroy(p->arc);
1585 janus_recorder_destroy(p->vrc);
1586 janus_recorder_destroy(p->drc);
1587
1588 if(p->udp_sock > 0)
1589 close(p->udp_sock);
1590 g_hash_table_destroy(p->rtp_forwarders);
1591 p->rtp_forwarders = NULL;
1592 g_hash_table_destroy(p->srtp_contexts);
1593 p->srtp_contexts = NULL;
1594 g_slist_free(p->subscribers);
1595
1596 janus_mutex_destroy(&p->subscribers_mutex);
1597 janus_mutex_destroy(&p->rtp_forwarders_mutex);
1598 g_free(p);
1599}
1600
1601static void janus_videoroom_session_destroy(janus_videoroom_session *session) {
1602 if(session && g_atomic_int_compare_and_exchange(&session->destroyed, 0, 1))
1603 janus_refcount_decrease(&session->ref);
1604}
1605
1606static void janus_videoroom_session_free(const janus_refcount *session_ref) {
1607 janus_videoroom_session *session = janus_refcount_containerof(session_ref, janus_videoroom_session, ref);
1608 /* Remove the reference to the core plugin session */
1609 janus_refcount_decrease(&session->handle->ref);
1610 /* This session can be destroyed, free all the resources */
1611 janus_mutex_destroy(&session->mutex);
1612 g_free(session);
1613}
1614
1615static void janus_videoroom_room_dereference(janus_videoroom *room) {
1616 janus_refcount_decrease(&room->ref);
1617}
1618
1619static void janus_videoroom_room_destroy(janus_videoroom *room) {
1620 if(room && g_atomic_int_compare_and_exchange(&room->destroyed, 0, 1))
1621 janus_refcount_decrease(&room->ref);
1622}
1623
1624static void janus_videoroom_room_free(const janus_refcount *room_ref) {
1625 janus_videoroom *room = janus_refcount_containerof(room_ref, janus_videoroom, ref);
1626 /* This room can be destroyed, free all the resources */
1627 g_free(room->room_name);
1628 g_free(room->room_secret);
1629 g_free(room->room_pin);
1630 g_free(room->rec_dir);
1631 g_hash_table_destroy(room->participants);
1632 g_hash_table_destroy(room->private_ids);
1633 g_hash_table_destroy(room->allowed);
1634 g_free(room);
1635}
1636
1637static void janus_videoroom_message_free(janus_videoroom_message *msg) {
1638 if(!msg || msg == &exit_message)
1639 return;
1640
1641 if(msg->handle && msg->handle->plugin_handle) {
1642 janus_videoroom_session *session = (janus_videoroom_session *)msg->handle->plugin_handle;
1643 janus_refcount_decrease(&session->ref);
1644 }
1645 msg->handle = NULL;
1646
1647 g_free(msg->transaction);
1648 msg->transaction = NULL;
1649 if(msg->message)
1650 json_decref(msg->message);
1651 msg->message = NULL;
1652 if(msg->jsep)
1653 json_decref(msg->jsep);
1654 msg->jsep = NULL;
1655
1656 g_free(msg);
1657}
1658
1659static void janus_videoroom_codecstr(janus_videoroom *videoroom, char *audio_codecs, char *video_codecs, int str_len, const char *split) {
1660 if (audio_codecs) {
1661 audio_codecs[0] = 0;
1662 g_snprintf(audio_codecs, str_len, "%s", janus_audiocodec_name(videoroom->acodec[0]));
1663 if (videoroom->acodec[1] != JANUS_AUDIOCODEC_NONE) {
1664 g_strlcat(audio_codecs, split, str_len);
1665 g_strlcat(audio_codecs, janus_audiocodec_name(videoroom->acodec[1]), str_len);
1666 }
1667 if (videoroom->acodec[2] != JANUS_AUDIOCODEC_NONE) {
1668 g_strlcat(audio_codecs, split, str_len);
1669 g_strlcat(audio_codecs, janus_audiocodec_name(videoroom->acodec[2]), str_len);
1670 }
1671 }
1672 if (video_codecs) {
1673 video_codecs[0] = 0;
1674 g_snprintf(video_codecs, str_len, "%s", janus_videocodec_name(videoroom->vcodec[0]));
1675 if (videoroom->vcodec[1] != JANUS_VIDEOCODEC_NONE) {
1676 g_strlcat(video_codecs, split, str_len);
1677 g_strlcat(video_codecs, janus_videocodec_name(videoroom->vcodec[1]), str_len);
1678 }
1679 if (videoroom->vcodec[2] != JANUS_VIDEOCODEC_NONE) {
1680 g_strlcat(video_codecs, split, str_len);
1681 g_strlcat(video_codecs, janus_videocodec_name(videoroom->vcodec[2]), str_len);
1682 }
1683 }
1684}
1685
1686static void janus_videoroom_reqfir(janus_videoroom_publisher *publisher, const char *reason) {
1687 /* Send a FIR */
1688 char buf[20];
1689 janus_rtcp_fir((char *)&buf, 20, &publisher->fir_seq);
1690 JANUS_LOG(LOG_VERB, "%s sending FIR to %"SCNu64" (%s)\n", reason, publisher->user_id, publisher->display ? publisher->display : "??");
1691 gateway->relay_rtcp(publisher->session->handle, 1, buf, 20);
1692 /* Send a PLI too, just in case... */
1693 janus_rtcp_pli((char *)&buf, 12);
1694 JANUS_LOG(LOG_VERB, "%s sending PLI to %"SCNu64" (%s)\n", reason, publisher->user_id, publisher->display ? publisher->display : "??");
1695 gateway->relay_rtcp(publisher->session->handle, 1, buf, 12);
1696 /* Update the time of when we last sent a keyframe request */
1697 publisher->fir_latest = janus_get_monotonic_time();
1698}
1699
1700/* Error codes */
1701#define JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR 499
1702#define JANUS_VIDEOROOM_ERROR_NO_MESSAGE 421
1703#define JANUS_VIDEOROOM_ERROR_INVALID_JSON 422
1704#define JANUS_VIDEOROOM_ERROR_INVALID_REQUEST 423
1705#define JANUS_VIDEOROOM_ERROR_JOIN_FIRST 424
1706#define JANUS_VIDEOROOM_ERROR_ALREADY_JOINED 425
1707#define JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM 426
1708#define JANUS_VIDEOROOM_ERROR_ROOM_EXISTS 427
1709#define JANUS_VIDEOROOM_ERROR_NO_SUCH_FEED 428
1710#define JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT 429
1711#define JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT 430
1712#define JANUS_VIDEOROOM_ERROR_INVALID_SDP_TYPE 431
1713#define JANUS_VIDEOROOM_ERROR_PUBLISHERS_FULL 432
1714#define JANUS_VIDEOROOM_ERROR_UNAUTHORIZED 433
1715#define JANUS_VIDEOROOM_ERROR_ALREADY_PUBLISHED 434
1716#define JANUS_VIDEOROOM_ERROR_NOT_PUBLISHED 435
1717#define JANUS_VIDEOROOM_ERROR_ID_EXISTS 436
1718#define JANUS_VIDEOROOM_ERROR_INVALID_SDP 437
1719
1720
1721static guint32 janus_videoroom_rtp_forwarder_add_helper(janus_videoroom_publisher *p,
1722 const gchar *host, int port, int rtcp_port, int pt, uint32_t ssrc,
1723 gboolean simulcast, int srtp_suite, const char *srtp_crypto,
1724 int substream, gboolean is_video, gboolean is_data) {
1725 if(!p || !host) {
1726 return 0;
1727 }
1728 janus_mutex_lock(&p->rtp_forwarders_mutex);
1729 /* Do we need to bind to a port for RTCP? */
1730 int fd = -1;
1731 uint16_t local_rtcp_port = 0;
1732 if(!is_data && rtcp_port > -1) {
1733 fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
1734 if(fd < 0) {
1735 JANUS_LOG(LOG_ERR, "Error creating RTCP socket for new RTP forwarder... %d (%s)\n",
1736 errno, strerror(errno));
1737 return 0;
1738 }
1739 struct sockaddr_in address;
1740 socklen_t len = sizeof(address);
1741 memset(&address, 0, sizeof(address));
1742 address.sin_family = AF_INET;
1743 address.sin_port = htons(0); /* The RTCP port we received is the remote one */
1744 address.sin_addr.s_addr = INADDR_ANY;
1745 if(bind(fd, (struct sockaddr *)&address, sizeof(struct sockaddr)) < 0 ||
1746 getsockname(fd, (struct sockaddr *)&address, &len) < 0) {
1747 JANUS_LOG(LOG_ERR, "Error binding RTCP socket for new RTP forwarder... %d (%s)\n",
1748 errno, strerror(errno));
1749 close(fd);
1750 return 0;
1751 }
1752 local_rtcp_port = ntohs(address.sin_port);
1753 JANUS_LOG(LOG_VERB, "Bound local %s RTCP port: %"SCNu16"\n",
1754 is_video ? "video" : "audio", local_rtcp_port);
1755 }
1756 janus_videoroom_rtp_forwarder *forward = g_malloc0(sizeof(janus_videoroom_rtp_forwarder));
1757 forward->source = p;
1758 forward->rtcp_fd = fd;
1759 forward->local_rtcp_port = local_rtcp_port;
1760 forward->remote_rtcp_port = rtcp_port;
1761 /* First of all, let's check if we need to setup an SRTP forwarder */
1762 if(!is_data && srtp_suite > 0 && srtp_crypto != NULL) {
1763 /* First of all, let's check if there's already an RTP forwarder with
1764 * the same SRTP context: make sure SSRC and pt are the same too */
1765 char media[10] = {0};
1766 if(!is_video) {
1767 g_sprintf(media, "audio");
1768 } else if(is_video) {
1769 g_sprintf(media, "video%d", substream);
1770 }
1771 char srtp_id[256] = {0};
1772 g_snprintf(srtp_id, 255, "%s-%s-%"SCNu32"-%d", srtp_crypto, media, ssrc, pt);
1773 JANUS_LOG(LOG_VERB, "SRTP context ID: %s\n", srtp_id);
1774 janus_videoroom_srtp_context *srtp_ctx = g_hash_table_lookup(p->srtp_contexts, srtp_id);
1775 if(srtp_ctx != NULL) {
1776 JANUS_LOG(LOG_VERB, " -- Reusing existing SRTP context\n");
1777 srtp_ctx->count++;
1778 forward->srtp_ctx = srtp_ctx;
1779 } else {
1780 /* Nope, base64 decode the crypto string and set it as a new SRTP context */
1781 JANUS_LOG(LOG_VERB, " -- Creating new SRTP context\n");
1782 srtp_ctx = g_malloc0(sizeof(janus_videoroom_srtp_context));
1783 gsize len = 0;
1784 guchar *decoded = g_base64_decode(srtp_crypto, &len);
1785 if(len < SRTP_MASTER_LENGTH) {
1786 janus_mutex_unlock(&p->rtp_forwarders_mutex);
1787 JANUS_LOG(LOG_ERR, "Invalid SRTP crypto (%s)\n", srtp_crypto);
1788 g_free(decoded);
1789 g_free(srtp_ctx);
1790 if(forward->rtcp_fd > -1)
1791 close(forward->rtcp_fd);
1792 g_free(forward);
1793 return 0;
1794 }
1795 /* Set SRTP policy */
1796 srtp_policy_t *policy = &srtp_ctx->policy;
1797 srtp_crypto_policy_set_rtp_default(&(policy->rtp));
1798 if(srtp_suite == 32) {
1799 srtp_crypto_policy_set_aes_cm_128_hmac_sha1_32(&(policy->rtp));
1800 } else if(srtp_suite == 80) {
1801 srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&(policy->rtp));
1802 }
1803 policy->ssrc.type = ssrc_any_inbound;
1804 policy->key = decoded;
1805 policy->next = NULL;
1806 /* Create SRTP context */
1807 srtp_err_status_t res = srtp_create(&srtp_ctx->ctx, policy);
1808 if(res != srtp_err_status_ok) {
1809 /* Something went wrong... */
1810 janus_mutex_unlock(&p->rtp_forwarders_mutex);
1811 JANUS_LOG(LOG_ERR, "Error creating forwarder SRTP session: %d (%s)\n", res, janus_srtp_error_str(res));
1812 g_free(decoded);
1813 policy->key = NULL;
1814 g_free(srtp_ctx);
1815 if(forward->rtcp_fd > -1)
1816 close(forward->rtcp_fd);
1817 g_free(forward);
1818 return 0;
1819 }
1820 srtp_ctx->contexts = p->srtp_contexts;
1821 srtp_ctx->id = g_strdup(srtp_id);
1822 srtp_ctx->count = 1;
1823 g_hash_table_insert(p->srtp_contexts, srtp_ctx->id, srtp_ctx);
1824 forward->srtp_ctx = srtp_ctx;
1825 }
1826 forward->is_srtp = TRUE;
1827 }
1828 forward->is_video = is_video;
1829 forward->payload_type = pt;
1830 forward->ssrc = ssrc;
1831 forward->substream = substream;
1832 forward->is_data = is_data;
1833 forward->serv_addr.sin_family = AF_INET;
1834 inet_pton(AF_INET, host, &(forward->serv_addr.sin_addr));
1835 forward->serv_addr.sin_port = htons(port);
1836 if(is_video && simulcast) {
1837 forward->simulcast = TRUE;
1838 janus_rtp_switching_context_reset(&forward->context);
1839 janus_rtp_simulcasting_context_reset(&forward->sim_context);
1840 forward->sim_context.rid_ext_id = p->rid_extmap_id;
1841 forward->sim_context.substream_target = 2;
1842 forward->sim_context.templayer_target = 2;
1843 }
1844 janus_refcount_init(&forward->ref, janus_videoroom_rtp_forwarder_free);
1845 guint32 stream_id = janus_random_uint32();
1846 while(g_hash_table_lookup(p->rtp_forwarders, GUINT_TO_POINTER(stream_id)) != NULL) {
1847 stream_id = janus_random_uint32();
1848 }
1849 g_hash_table_insert(p->rtp_forwarders, GUINT_TO_POINTER(stream_id), forward);
1850 if(fd > -1) {
1851 /* We need RTCP: track this file descriptor, and ref the forwarder */
1852 janus_refcount_increase(&forward->ref);
1853 forward->rtcp_recv = g_source_new(&janus_videoroom_rtp_forwarder_rtcp_funcs, sizeof(janus_videoroom_rtcp_receiver));
1854 janus_videoroom_rtcp_receiver *rr = (janus_videoroom_rtcp_receiver *)forward->rtcp_recv;
1855 rr->forward = forward;
1856 g_source_set_priority(forward->rtcp_recv, G_PRIORITY_DEFAULT);
1857 g_source_add_unix_fd(forward->rtcp_recv, fd, G_IO_IN | G_IO_ERR);
1858 g_source_attach((GSource *)forward->rtcp_recv, rtcpfwd_ctx);
1859 /* Send a couple of empty RTP packets to the remote port to do latching */
1860 struct sockaddr_in address;
1861 socklen_t addrlen = sizeof(address);
1862 memset(&address, 0, addrlen);
1863 address.sin_family = AF_INET;
1864 address.sin_addr.s_addr = forward->serv_addr.sin_addr.s_addr;
1865 address.sin_port = htons(forward->remote_rtcp_port);
1866 janus_rtp_header rtp;
1867 memset(&rtp, 0, sizeof(rtp));
1868 rtp.version = 2;
1869 (void)sendto(fd, &rtp, 12, 0, (struct sockaddr *)&address, addrlen);
1870 (void)sendto(fd, &rtp, 12, 0, (struct sockaddr *)&address, addrlen);
1871 }
1872 janus_mutex_unlock(&p->rtp_forwarders_mutex);
1873 JANUS_LOG(LOG_VERB, "Added %s/%d rtp_forward to participant %"SCNu64" host: %s:%d stream_id: %"SCNu32"\n",
1874 is_data ? "data" : (is_video ? "video" : "audio"), substream, p->user_id, host, port, stream_id);
1875 return stream_id;
1876}
1877
1878static void janus_videoroom_rtp_forwarder_destroy(janus_videoroom_rtp_forwarder *forward) {
1879 if(forward && g_atomic_int_compare_and_exchange(&forward->destroyed, 0, 1)) {
1880 if(forward->rtcp_fd > -1) {
1881 g_source_destroy(forward->rtcp_recv);
1882 g_source_unref(forward->rtcp_recv);
1883 }
1884 janus_refcount_decrease(&forward->ref);
1885 }
1886}
1887static void janus_videoroom_rtp_forwarder_free(const janus_refcount *f_ref) {
1888 janus_videoroom_rtp_forwarder *forward = janus_refcount_containerof(f_ref, janus_videoroom_rtp_forwarder, ref);
1889 if(forward->rtcp_fd > -1)
1890 close(forward->rtcp_fd);
1891 if(forward->is_srtp && forward->srtp_ctx) {
1892 forward->srtp_ctx->count--;
1893 if(forward->srtp_ctx->count == 0 && forward->srtp_ctx->contexts != NULL)
1894 g_hash_table_remove(forward->srtp_ctx->contexts, forward->srtp_ctx->id);
1895 }
1896 g_free(forward);
1897 forward = NULL;
1898}
1899
1900static void janus_videoroom_srtp_context_free(gpointer data) {
1901 if(data) {
1902 janus_videoroom_srtp_context *srtp_ctx = (janus_videoroom_srtp_context *)data;
1903 if(srtp_ctx) {
1904 g_free(srtp_ctx->id);
1905 srtp_dealloc(srtp_ctx->ctx);
1906 g_free(srtp_ctx->policy.key);
1907 g_free(srtp_ctx);
1908 srtp_ctx = NULL;
1909 }
1910 }
1911}
1912
1913
1914/* Plugin implementation */
1915int janus_videoroom_init(janus_callbacks *callback, const char *config_path) {
1916 if(g_atomic_int_get(&stopping)) {
1917 /* Still stopping from before */
1918 return -1;
1919 }
1920 if(callback == NULL || config_path == NULL) {
1921 /* Invalid arguments */
1922 return -1;
1923 }
1924
1925 /* Read configuration */
1926 char filename[255];
1927 g_snprintf(filename, 255, "%s/%s.jcfg", config_path, JANUS_VIDEOROOM_PACKAGE);
1928 JANUS_LOG(LOG_VERB, "Configuration file: %s\n", filename);
1929 config = janus_config_parse(filename);
1930 if(config == NULL) {
1931 JANUS_LOG(LOG_WARN, "Couldn't find .jcfg configuration file (%s), trying .cfg\n", JANUS_VIDEOROOM_PACKAGE);
1932 g_snprintf(filename, 255, "%s/%s.cfg", config_path, JANUS_VIDEOROOM_PACKAGE);
1933 JANUS_LOG(LOG_VERB, "Configuration file: %s\n", filename);
1934 config = janus_config_parse(filename);
1935 }
1936 config_folder = config_path;
1937 if(config != NULL)
1938 janus_config_print(config);
1939
1940 rooms = g_hash_table_new_full(g_int64_hash, g_int64_equal,
1941 (GDestroyNotify)g_free, (GDestroyNotify) janus_videoroom_room_destroy);
1942 sessions = g_hash_table_new_full(NULL, NULL, NULL, (GDestroyNotify)janus_videoroom_session_destroy);
1943
1944 messages = g_async_queue_new_full((GDestroyNotify) janus_videoroom_message_free);
1945
1946 /* This is the callback we'll need to invoke to contact the Janus core */
1947 gateway = callback;
1948
1949 /* Parse configuration to populate the rooms list */
1950 if(config != NULL) {
1951 janus_config_category *config_general = janus_config_get_create(config, NULL, janus_config_type_category, "general");
1952 /* Any admin key to limit who can "create"? */
1953 janus_config_item *key = janus_config_get(config, config_general, janus_config_type_item, "admin_key");
1954 if(key != NULL && key->value != NULL)
1955 admin_key = g_strdup(key->value);
1956 janus_config_item *lrf = janus_config_get(config, config_general, janus_config_type_item, "lock_rtp_forward");
1957 if(admin_key && lrf != NULL && lrf->value != NULL)
1958 lock_rtpfwd = janus_is_true(lrf->value);
1959 janus_config_item *events = janus_config_get(config, config_general, janus_config_type_item, "events");
1960 if(events != NULL && events->value != NULL)
1961 notify_events = janus_is_true(events->value);
1962 if(!notify_events && callback->events_is_enabled()) {
1963 JANUS_LOG(LOG_WARN, "Notification of events to handlers disabled for %s\n", JANUS_VIDEOROOM_NAME);
1964 }
1965 /* Iterate on all rooms */
1966 GList *clist = janus_config_get_categories(config, NULL), *cl = clist;
1967 while(cl != NULL) {
1968 janus_config_category *cat = (janus_config_category *)cl->data;
1969 if(cat->name == NULL || !strcasecmp(cat->name, "general")) {
1970 cl = cl->next;
1971 continue;
1972 }
1973 JANUS_LOG(LOG_VERB, "Adding video room '%s'\n", cat->name);
1974 janus_config_item *desc = janus_config_get(config, cat, janus_config_type_item, "description");
1975 janus_config_item *priv = janus_config_get(config, cat, janus_config_type_item, "is_private");
1976 janus_config_item *secret = janus_config_get(config, cat, janus_config_type_item, "secret");
1977 janus_config_item *pin = janus_config_get(config, cat, janus_config_type_item, "pin");
1978 janus_config_item *req_pvtid = janus_config_get(config, cat, janus_config_type_item, "require_pvtid");
1979 janus_config_item *bitrate = janus_config_get(config, cat, janus_config_type_item, "bitrate");
1980 janus_config_item *bitrate_cap = janus_config_get(config, cat, janus_config_type_item, "bitrate_cap");
1981 janus_config_item *maxp = janus_config_get(config, cat, janus_config_type_item, "publishers");
1982 janus_config_item *firfreq = janus_config_get(config, cat, janus_config_type_item, "fir_freq");
1983 janus_config_item *audiocodec = janus_config_get(config, cat, janus_config_type_item, "audiocodec");
1984 janus_config_item *videocodec = janus_config_get(config, cat, janus_config_type_item, "videocodec");
1985 janus_config_item *fec = janus_config_get(config, cat, janus_config_type_item, "opus_fec");
1986 janus_config_item *svc = janus_config_get(config, cat, janus_config_type_item, "video_svc");
1987 janus_config_item *audiolevel_ext = janus_config_get(config, cat, janus_config_type_item, "audiolevel_ext");
1988 janus_config_item *audiolevel_event = janus_config_get(config, cat, janus_config_type_item, "audiolevel_event");
1989 janus_config_item *audio_active_packets = janus_config_get(config, cat, janus_config_type_item, "audio_active_packets");
1990 janus_config_item *audio_level_average = janus_config_get(config, cat, janus_config_type_item, "audio_level_average");
1991 janus_config_item *videoorient_ext = janus_config_get(config, cat, janus_config_type_item, "videoorient_ext");
1992 janus_config_item *playoutdelay_ext = janus_config_get(config, cat, janus_config_type_item, "playoutdelay_ext");
1993 janus_config_item *transport_wide_cc_ext = janus_config_get(config, cat, janus_config_type_item, "transport_wide_cc_ext");
1994 janus_config_item *notify_joining = janus_config_get(config, cat, janus_config_type_item, "notify_joining");
1995 janus_config_item *record = janus_config_get(config, cat, janus_config_type_item, "record");
1996 janus_config_item *rec_dir = janus_config_get(config, cat, janus_config_type_item, "rec_dir");
1997 /* Create the video room */
1998 janus_videoroom *videoroom = g_malloc0(sizeof(janus_videoroom));
1999 const char *room_num = cat->name;
2000 if(strstr(room_num, "room-") == room_num)
2001 room_num += 5;
2002 videoroom->room_id = g_ascii_strtoull(room_num, NULL, 0);
2003 char *description = NULL;
2004 if(desc != NULL && desc->value != NULL && strlen(desc->value) > 0)
2005 description = g_strdup(desc->value);
2006 else
2007 description = g_strdup(cat->name);
2008 videoroom->room_name = description;
2009 if(secret != NULL && secret->value != NULL) {
2010 videoroom->room_secret = g_strdup(secret->value);
2011 }
2012 if(pin != NULL && pin->value != NULL) {
2013 videoroom->room_pin = g_strdup(pin->value);
2014 }
2015 videoroom->is_private = priv && priv->value && janus_is_true(priv->value);
2016 videoroom->require_pvtid = req_pvtid && req_pvtid->value && janus_is_true(req_pvtid->value);
2017 videoroom->max_publishers = 3; /* FIXME How should we choose a default? */
2018 if(maxp != NULL && maxp->value != NULL)
2019 videoroom->max_publishers = atol(maxp->value);
2020 if(videoroom->max_publishers < 0)
2021 videoroom->max_publishers = 3; /* FIXME How should we choose a default? */
2022 videoroom->bitrate = 0;
2023 if(bitrate != NULL && bitrate->value != NULL)
2024 videoroom->bitrate = atol(bitrate->value);
2025 if(videoroom->bitrate > 0 && videoroom->bitrate < 64000)
2026 videoroom->bitrate = 64000; /* Don't go below 64k */
2027 videoroom->bitrate_cap = bitrate_cap && bitrate_cap->value && janus_is_true(bitrate_cap->value);
2028 videoroom->fir_freq = 0;
2029 if(firfreq != NULL && firfreq->value != NULL)
2030 videoroom->fir_freq = atol(firfreq->value);
2031 /* By default, we force Opus as the only audio codec */
2032 videoroom->acodec[0] = JANUS_AUDIOCODEC_OPUS;
2033 videoroom->acodec[1] = JANUS_AUDIOCODEC_NONE;
2034 videoroom->acodec[2] = JANUS_AUDIOCODEC_NONE;
2035 /* Check if we're forcing a different single codec, or allowing more than one */
2036 if(audiocodec && audiocodec->value) {
2037 gchar **list = g_strsplit(audiocodec->value, ",", 4);
2038 gchar *codec = list[0];
2039 if(codec != NULL) {
2040 int i=0;
2041 while(codec != NULL) {
2042 if(i == 3) {
2043 JANUS_LOG(LOG_WARN, "Ignoring extra audio codecs: %s\n", codec);
2044 break;
2045 }
2046 if(strlen(codec) > 0)
2047 videoroom->acodec[i] = janus_audiocodec_from_name(codec);
2048 i++;
2049 codec = list[i];
2050 }
2051 }
2052 g_clear_pointer(&list, g_strfreev);
2053 }
2054 /* By default, we force VP8 as the only video codec */
2055 videoroom->vcodec[0] = JANUS_VIDEOCODEC_VP8;
2056 videoroom->vcodec[1] = JANUS_VIDEOCODEC_NONE;
2057 videoroom->vcodec[2] = JANUS_VIDEOCODEC_NONE;
2058 /* Check if we're forcing a different single codec, or allowing more than one */
2059 if(videocodec && videocodec->value) {
2060 gchar **list = g_strsplit(videocodec->value, ",", 4);
2061 gchar *codec = list[0];
2062 if(codec != NULL) {
2063 int i=0;
2064 while(codec != NULL) {
2065 if(i == 3) {
2066 JANUS_LOG(LOG_WARN, "Ignoring extra video codecs: %s\n", codec);
2067 break;
2068 }
2069 if(strlen(codec) > 0)
2070 videoroom->vcodec[i] = janus_videocodec_from_name(codec);
2071 i++;
2072 codec = list[i];
2073 }
2074 }
2075 g_clear_pointer(&list, g_strfreev);
2076 }
2077 if(fec && fec->value) {
2078 videoroom->do_opusfec = janus_is_true(fec->value);
2079 if(videoroom->acodec[0] != JANUS_AUDIOCODEC_OPUS &&
2080 videoroom->acodec[1] != JANUS_AUDIOCODEC_OPUS &&
2081 videoroom->acodec[2] != JANUS_AUDIOCODEC_OPUS) {
2082 videoroom->do_opusfec = FALSE;
2083 JANUS_LOG(LOG_WARN, "Inband FEC is only supported for rooms that allow Opus: disabling it...\n");
2084 }
2085 }
2086 if(svc && svc->value && janus_is_true(svc->value)) {
2087 if(videoroom->vcodec[0] == JANUS_VIDEOCODEC_VP9 &&
2088 videoroom->vcodec[1] == JANUS_VIDEOCODEC_NONE &&
2089 videoroom->vcodec[2] == JANUS_VIDEOCODEC_NONE) {
2090 videoroom->do_svc = TRUE;
2091 } else {
2092 JANUS_LOG(LOG_WARN, "SVC is only supported, in an experimental way, for VP9 only rooms: disabling it...\n");
2093 }
2094 }
2095 videoroom->audiolevel_ext = TRUE;
2096 if(audiolevel_ext != NULL && audiolevel_ext->value != NULL)
2097 videoroom->audiolevel_ext = janus_is_true(audiolevel_ext->value);
2098 videoroom->audiolevel_event = FALSE;
2099 if(audiolevel_event != NULL && audiolevel_event->value != NULL)
2100 videoroom->audiolevel_event = janus_is_true(audiolevel_event->value);
2101 if(videoroom->audiolevel_event) {
2102 videoroom->audio_active_packets = 100;
2103 if(audio_active_packets != NULL && audio_active_packets->value != NULL){
2104 if(atoi(audio_active_packets->value) > 0) {
2105 videoroom->audio_active_packets = atoi(audio_active_packets->value);
2106 } else {
2107 JANUS_LOG(LOG_WARN, "Invalid audio_active_packets value, using default: %d\n", videoroom->audio_active_packets);
2108 }
2109 }
2110 videoroom->audio_level_average = 25;
2111 if(audio_level_average != NULL && audio_level_average->value != NULL) {
2112 if(atoi(audio_level_average->value) > 0) {
2113 videoroom->audio_level_average = atoi(audio_level_average->value);
2114 } else {
2115 JANUS_LOG(LOG_WARN, "Invalid audio_level_average value provided, using default: %d\n", videoroom->audio_level_average);
2116 }
2117 }
2118 }
2119 videoroom->videoorient_ext = TRUE;
2120 if(videoorient_ext != NULL && videoorient_ext->value != NULL)
2121 videoroom->videoorient_ext = janus_is_true(videoorient_ext->value);
2122 videoroom->playoutdelay_ext = TRUE;
2123 if(playoutdelay_ext != NULL && playoutdelay_ext->value != NULL)
2124 videoroom->playoutdelay_ext = janus_is_true(playoutdelay_ext->value);
2125 videoroom->transport_wide_cc_ext = TRUE;
2126 if(transport_wide_cc_ext != NULL && transport_wide_cc_ext->value != NULL)
2127 videoroom->transport_wide_cc_ext = janus_is_true(transport_wide_cc_ext->value);
2128 if(record && record->value) {
2129 videoroom->record = janus_is_true(record->value);
2130 }
2131 if(rec_dir && rec_dir->value) {
2132 videoroom->rec_dir = g_strdup(rec_dir->value);
2133 }
2134 /* By default, the videoroom plugin does not notify about participants simply joining the room.
2135 It only notifies when the participant actually starts publishing media. */
2136 videoroom->notify_joining = FALSE;
2137 if(notify_joining != NULL && notify_joining->value != NULL)
2138 videoroom->notify_joining = janus_is_true(notify_joining->value);
2139 g_atomic_int_set(&videoroom->destroyed, 0);
2140 janus_mutex_init(&videoroom->mutex);
2141 janus_refcount_init(&videoroom->ref, janus_videoroom_room_free);
2142 videoroom->participants = g_hash_table_new_full(g_int64_hash, g_int64_equal, (GDestroyNotify)g_free, (GDestroyNotify)janus_videoroom_publisher_dereference);
2143 videoroom->private_ids = g_hash_table_new(NULL, NULL);
2144 videoroom->check_allowed = FALSE; /* Static rooms can't have an "allowed" list yet, no hooks to the configuration file */
2145 videoroom->allowed = g_hash_table_new_full(g_str_hash, g_str_equal, (GDestroyNotify)g_free, NULL);
2146 janus_mutex_lock(&rooms_mutex);
2147 g_hash_table_insert(rooms, janus_uint64_dup(videoroom->room_id), videoroom);
2148 janus_mutex_unlock(&rooms_mutex);
2149 /* Compute a list of the supported codecs for the summary */
2150 char audio_codecs[100], video_codecs[100];
2151 janus_videoroom_codecstr(videoroom, audio_codecs, video_codecs, sizeof(audio_codecs), "|");
2152 JANUS_LOG(LOG_VERB, "Created videoroom: %"SCNu64" (%s, %s, %s/%s codecs, secret: %s, pin: %s, pvtid: %s)\n",
2153 videoroom->room_id, videoroom->room_name,
2154 videoroom->is_private ? "private" : "public",
2155 audio_codecs, video_codecs,
2156 videoroom->room_secret ? videoroom->room_secret : "no secret",
2157 videoroom->room_pin ? videoroom->room_pin : "no pin",
2158 videoroom->require_pvtid ? "required" : "optional");
2159 if(videoroom->record) {
2160 JANUS_LOG(LOG_VERB, " -- Room is going to be recorded in %s\n", videoroom->rec_dir ? videoroom->rec_dir : "the current folder");
2161 }
2162 cl = cl->next;
2163 }
2164 /* Done: we keep the configuration file open in case we get a "create" or "destroy" with permanent=true */
2165 }
2166
2167 /* Show available rooms */
2168 janus_mutex_lock(&rooms_mutex);
2169 GHashTableIter iter;
2170 gpointer value;
2171 g_hash_table_iter_init(&iter, rooms);
2172 while (g_hash_table_iter_next(&iter, NULL, &value)) {
2173 janus_videoroom *vr = value;
2174 /* Compute a list of the supported codecs for the summary */
2175 char audio_codecs[100], video_codecs[100];
2176 janus_videoroom_codecstr(vr, audio_codecs, video_codecs, sizeof(audio_codecs), "|");
2177 JANUS_LOG(LOG_VERB, " ::: [%"SCNu64"][%s] %"SCNu32", max %d publishers, FIR frequency of %d seconds, %s audio codec(s), %s video codec(s)\n",
2178 vr->room_id, vr->room_name, vr->bitrate, vr->max_publishers, vr->fir_freq,
2179 audio_codecs, video_codecs);
2180 }
2181 janus_mutex_unlock(&rooms_mutex);
2182
2183 /* Thread for handling incoming RTCP packets from RTP forwarders, if any */
2184 rtcpfwd_ctx = g_main_context_new();
2185 rtcpfwd_loop = g_main_loop_new(rtcpfwd_ctx, FALSE);
2186 GError *error = NULL;
2187 rtcpfwd_thread = g_thread_try_new("videoroom rtcpfwd", janus_videoroom_rtp_forwarder_rtcp_thread, NULL, &error);
2188 if(error != NULL) {
2189 /* We show the error but it's not fatal */
2190 JANUS_LOG(LOG_ERR, "Got error %d (%s) trying to launch the VideoRoom RTCP thread for RTP forwarders...\n",
2191 error->code, error->message ? error->message : "??");
2192 }
2193
2194 g_atomic_int_set(&initialized, 1);
2195
2196 /* Launch the thread that will handle incoming messages */
2197 error = NULL;
2198 handler_thread = g_thread_try_new("videoroom handler", janus_videoroom_handler, NULL, &error);
2199 if(error != NULL) {
2200 g_atomic_int_set(&initialized, 0);
2201 JANUS_LOG(LOG_ERR, "Got error %d (%s) trying to launch the VideoRoom handler thread...\n",
2202 error->code, error->message ? error->message : "??");
2203 janus_config_destroy(config);
2204 return -1;
2205 }
2206 JANUS_LOG(LOG_INFO, "%s initialized!\n", JANUS_VIDEOROOM_NAME);
2207 return 0;
2208}
2209
2210void janus_videoroom_destroy(void) {
2211 if(!g_atomic_int_get(&initialized))
2212 return;
2213 g_atomic_int_set(&stopping, 1);
2214
2215 g_async_queue_push(messages, &exit_message);
2216 if(handler_thread != NULL) {
2217 g_thread_join(handler_thread);
2218 handler_thread = NULL;
2219 }
2220 if(rtcpfwd_thread != NULL) {
2221 if(g_main_loop_is_running(rtcpfwd_loop)) {
2222 g_main_loop_quit(rtcpfwd_loop);
2223 g_main_context_wakeup(rtcpfwd_ctx);
2224 }
2225 g_thread_join(rtcpfwd_thread);
2226 rtcpfwd_thread = NULL;
2227 }
2228
2229 /* FIXME We should destroy the sessions cleanly */
2230 janus_mutex_lock(&sessions_mutex);
2231 g_hash_table_destroy(sessions);
2232 sessions = NULL;
2233 janus_mutex_unlock(&sessions_mutex);
2234
2235 janus_mutex_lock(&rooms_mutex);
2236 g_hash_table_destroy(rooms);
2237 rooms = NULL;
2238 janus_mutex_unlock(&rooms_mutex);
2239
2240 g_async_queue_unref(messages);
2241 messages = NULL;
2242
2243 janus_config_destroy(config);
2244 g_free(admin_key);
2245
2246 g_atomic_int_set(&initialized, 0);
2247 g_atomic_int_set(&stopping, 0);
2248 JANUS_LOG(LOG_INFO, "%s destroyed!\n", JANUS_VIDEOROOM_NAME);
2249}
2250
2251int janus_videoroom_get_api_compatibility(void) {
2252 /* Important! This is what your plugin MUST always return: don't lie here or bad things will happen */
2253 return JANUS_PLUGIN_API_VERSION;
2254}
2255
2256int janus_videoroom_get_version(void) {
2257 return JANUS_VIDEOROOM_VERSION;
2258}
2259
2260const char *janus_videoroom_get_version_string(void) {
2261 return JANUS_VIDEOROOM_VERSION_STRING;
2262}
2263
2264const char *janus_videoroom_get_description(void) {
2265 return JANUS_VIDEOROOM_DESCRIPTION;
2266}
2267
2268const char *janus_videoroom_get_name(void) {
2269 return JANUS_VIDEOROOM_NAME;
2270}
2271
2272const char *janus_videoroom_get_author(void) {
2273 return JANUS_VIDEOROOM_AUTHOR;
2274}
2275
2276const char *janus_videoroom_get_package(void) {
2277 return JANUS_VIDEOROOM_PACKAGE;
2278}
2279
2280static janus_videoroom_session *janus_videoroom_lookup_session(janus_plugin_session *handle) {
2281 janus_videoroom_session *session = NULL;
2282 if (g_hash_table_contains(sessions, handle)) {
2283 session = (janus_videoroom_session *)handle->plugin_handle;
2284 }
2285 return session;
2286}
2287
2288void janus_videoroom_create_session(janus_plugin_session *handle, int *error) {
2289 if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized)) {
2290 *error = -1;
2291 return;
2292 }
2293 janus_videoroom_session *session = g_malloc0(sizeof(janus_videoroom_session));
2294 session->handle = handle;
2295 session->participant_type = janus_videoroom_p_type_none;
2296 session->participant = NULL;
2297 g_atomic_int_set(&session->hangingup, 0);
2298 g_atomic_int_set(&session->destroyed, 0);
2299 handle->plugin_handle = session;
2300 janus_mutex_init(&session->mutex);
2301 janus_refcount_init(&session->ref, janus_videoroom_session_free);
2302
2303 janus_mutex_lock(&sessions_mutex);
2304 g_hash_table_insert(sessions, handle, session);
2305 janus_mutex_unlock(&sessions_mutex);
2306
2307 return;
2308}
2309
2310static janus_videoroom_publisher *janus_videoroom_session_get_publisher(janus_videoroom_session *session) {
2311 janus_mutex_lock(&session->mutex);
2312 janus_videoroom_publisher *publisher = (janus_videoroom_publisher *)session->participant;
2313 if(publisher)
2314 janus_refcount_increase(&publisher->ref);
2315 janus_mutex_unlock(&session->mutex);
2316 return publisher;
2317}
2318
2319static janus_videoroom_publisher *janus_videoroom_session_get_publisher_nodebug(janus_videoroom_session *session) {
2320 janus_mutex_lock(&session->mutex);
2321 janus_videoroom_publisher *publisher = (janus_videoroom_publisher *)session->participant;
2322 if(publisher)
2323 janus_refcount_increase_nodebug(&publisher->ref);
2324 janus_mutex_unlock(&session->mutex);
2325 return publisher;
2326}
2327
2328static void janus_videoroom_notify_participants(janus_videoroom_publisher *participant, json_t *msg) {
2329 /* participant->room->mutex has to be locked. */
2330 if(participant->room == NULL)
2331 return;
2332 GHashTableIter iter;
2333 gpointer value;
2334 g_hash_table_iter_init(&iter, participant->room->participants);
2335 while (participant->room && !g_atomic_int_get(&participant->room->destroyed) && g_hash_table_iter_next(&iter, NULL, &value)) {
2336 janus_videoroom_publisher *p = value;
2337 if(p && p->session && p != participant) {
2338 JANUS_LOG(LOG_VERB, "Notifying participant %"SCNu64" (%s)\n", p->user_id, p->display ? p->display : "??");
2339 int ret = gateway->push_event(p->session->handle, &janus_videoroom_plugin, NULL, msg, NULL);
2340 JANUS_LOG(LOG_VERB, " >> %d (%s)\n", ret, janus_get_api_error(ret));
2341 }
2342 }
2343}
2344
2345static void janus_videoroom_participant_joining(janus_videoroom_publisher *p) {
2346 /* we need to check if the room still exists, may have been destroyed already */
2347 if(p->room == NULL)
2348 return;
2349 if(!g_atomic_int_get(&p->room->destroyed) && p->room->notify_joining) {
2350 json_t *event = json_object();
2351 json_t *user = json_object();
2352 json_object_set_new(user, "id", json_integer(p->user_id));
2353 if (p->display) {
2354 json_object_set_new(user, "display", json_string(p->display));
2355 }
2356 json_object_set_new(event, "videoroom", json_string("event"));
2357 json_object_set_new(event, "room", json_integer(p->room_id));
2358 json_object_set_new(event, "joining", user);
2359 janus_videoroom_notify_participants(p, event);
2360 /* user gets deref-ed by the owner event */
2361 json_decref(event);
2362 }
2363}
2364
2365static void janus_videoroom_leave_or_unpublish(janus_videoroom_publisher *participant, gboolean is_leaving, gboolean kicked) {
2366 /* we need to check if the room still exists, may have been destroyed already */
2367 if(participant->room == NULL)
2368 return;
2369 janus_mutex_lock(&rooms_mutex);
2370 if (!g_hash_table_lookup(rooms, &participant->room_id)) {
2371 JANUS_LOG(LOG_ERR, "No such room (%"SCNu64")\n", participant->room_id);
2372 janus_mutex_unlock(&rooms_mutex);
2373 return;
2374 }
2375 janus_mutex_unlock(&rooms_mutex);
2376 if(!participant->room || g_atomic_int_get(&participant->room->destroyed))
2377 return;
2378 json_t *event = json_object();
2379 json_object_set_new(event, "videoroom", json_string("event"));
2380 json_object_set_new(event, "room", json_integer(participant->room_id));
2381 json_object_set_new(event, is_leaving ? (kicked ? "kicked" : "leaving") : "unpublished",
2382 json_integer(participant->user_id));
2383 janus_mutex_lock(&participant->room->mutex);
2384 janus_videoroom_notify_participants(participant, event);
2385 /* Also notify event handlers */
2386 if(notify_events && gateway->events_is_enabled()) {
2387 json_t *info = json_object();
2388 json_object_set_new(info, "event", json_string(is_leaving ? (kicked ? "kicked" : "leaving") : "unpublished"));
2389 json_object_set_new(info, "room", json_integer(participant->room_id));
2390 json_object_set_new(info, "id", json_integer(participant->user_id));
2391 gateway->notify_event(&janus_videoroom_plugin, NULL, info);
2392 }
2393 if(is_leaving) {
2394 g_hash_table_remove(participant->room->participants, &participant->user_id);
2395 g_hash_table_remove(participant->room->private_ids, GUINT_TO_POINTER(participant->pvt_id));
2396 }
2397 janus_mutex_unlock(&participant->room->mutex);
2398 json_decref(event);
2399}
2400
2401void janus_videoroom_destroy_session(janus_plugin_session *handle, int *error) {
2402 if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized)) {
2403 *error = -1;
2404 return;
2405 }
2406 janus_mutex_lock(&sessions_mutex);
2407 janus_videoroom_session *session = janus_videoroom_lookup_session(handle);
2408 if(!session) {
2409 janus_mutex_unlock(&sessions_mutex);
2410 JANUS_LOG(LOG_ERR, "No VideoRoom session associated with this handle...\n");
2411 *error = -2;
2412 return;
2413 }
2414 if(g_atomic_int_get(&session->destroyed)) {
2415 janus_mutex_unlock(&sessions_mutex);
2416 JANUS_LOG(LOG_WARN, "VideoRoom session already marked as destroyed...\n");
2417 return;
2418 }
2419 /* Cleaning up and removing the session is done in a lazy way */
2420 if(!g_atomic_int_get(&session->destroyed)) {
2421 /* Any related WebRTC PeerConnection is not available anymore either */
2422 janus_videoroom_hangup_media_internal(handle);
2423 if(session->participant_type == janus_videoroom_p_type_publisher) {
2424 /* Get rid of publisher */
2425 janus_mutex_lock(&session->mutex);
2426 janus_videoroom_publisher *p = (janus_videoroom_publisher *)session->participant;
2427 if(p)
2428 janus_refcount_increase(&p->ref);
2429 session->participant = NULL;
2430 janus_mutex_unlock(&session->mutex);
2431 if(p && p->room) {
2432 janus_videoroom_leave_or_unpublish(p, TRUE, FALSE);
2433 /* Don't clear p->room. Another thread calls janus_videoroom_leave_or_unpublish,
2434 too, and there is no mutex to protect this change. */
2435 g_clear_pointer(&p->room, janus_videoroom_room_dereference);
2436 }
2437 janus_videoroom_publisher_destroy(p);
2438 if(p)
2439 janus_refcount_decrease(&p->ref);
2440 } else if(session->participant_type == janus_videoroom_p_type_subscriber) {
2441 janus_videoroom_subscriber *s = (janus_videoroom_subscriber *)session->participant;
2442 if(!s->close_pc) {
2443 if (s->room)
2444 g_clear_pointer(&s->room, janus_videoroom_room_dereference);
2445 janus_refcount_decrease(&s->ref);
2446 }
2447 session->participant = NULL;
2448 if(s->room) {
2449 janus_refcount_decrease(&s->room->ref);
2450 }
2451 janus_videoroom_subscriber_destroy(s);
2452 }
2453 g_hash_table_remove(sessions, handle);
2454 }
2455 janus_mutex_unlock(&sessions_mutex);
2456 return;
2457}
2458
2459json_t *janus_videoroom_query_session(janus_plugin_session *handle) {
2460 if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized)) {
2461 return NULL;
2462 }
2463 janus_mutex_lock(&sessions_mutex);
2464 janus_videoroom_session *session = janus_videoroom_lookup_session(handle);
2465 if(!session) {
2466 janus_mutex_unlock(&sessions_mutex);
2467 JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
2468 return NULL;
2469 }
2470 janus_refcount_increase(&session->ref);
2471 janus_mutex_unlock(&sessions_mutex);
2472 /* Show the participant/room info, if any */
2473 json_t *info = json_object();
2474 if(session->participant) {
2475 if(session->participant_type == janus_videoroom_p_type_none) {
2476 json_object_set_new(info, "type", json_string("none"));
2477 } else if(session->participant_type == janus_videoroom_p_type_publisher) {
2478 json_object_set_new(info, "type", json_string("publisher"));
2479 janus_videoroom_publisher *participant = janus_videoroom_session_get_publisher(session);
2480 if(participant && participant->room) {
2481 janus_videoroom *room = participant->room;
2482 json_object_set_new(info, "room", room ? json_integer(room->room_id) : NULL);
2483 json_object_set_new(info, "id", json_integer(participant->user_id));
2484 json_object_set_new(info, "private_id", json_integer(participant->pvt_id));
2485 if(participant->display)
2486 json_object_set_new(info, "display", json_string(participant->display));
2487 if(participant->subscribers)
2488 json_object_set_new(info, "viewers", json_integer(g_slist_length(participant->subscribers)));
2489 json_t *media = json_object();
2490 json_object_set_new(media, "audio", participant->audio ? json_true() : json_false());
2491 if(participant->audio)
2492 json_object_set_new(media, "audio_codec", json_string(janus_audiocodec_name(participant->acodec)));
2493 json_object_set_new(media, "video", participant->video ? json_true() : json_false());
2494 if(participant->video)
2495 json_object_set_new(media, "video_codec", json_string(janus_videocodec_name(participant->vcodec)));
2496 json_object_set_new(media, "data", participant->data ? json_true() : json_false());
2497 json_object_set_new(info, "media", media);
2498 json_object_set_new(info, "bitrate", json_integer(participant->bitrate));
2499 if(participant->ssrc[0] != 0 || participant->rid[0] != NULL)
2500 json_object_set_new(info, "simulcast", json_true());
2501 if(participant->arc || participant->vrc || participant->drc) {
2502 json_t *recording = json_object();
2503 if(participant->arc && participant->arc->filename)
2504 json_object_set_new(recording, "audio", json_string(participant->arc->filename));
2505 if(participant->vrc && participant->vrc->filename)
2506 json_object_set_new(recording, "video", json_string(participant->vrc->filename));
2507 if(participant->drc && participant->drc->filename)
2508 json_object_set_new(recording, "data", json_string(participant->drc->filename));
2509 json_object_set_new(info, "recording", recording);
2510 }
2511 if(participant->audio_level_extmap_id > 0) {
2512 json_object_set_new(info, "audio-level-dBov", json_integer(participant->audio_dBov_level));
2513 json_object_set_new(info, "talking", participant->talking ? json_true() : json_false());
2514 }
2515 janus_refcount_decrease(&participant->ref);
2516 }
2517 } else if(session->participant_type == janus_videoroom_p_type_subscriber) {
2518 json_object_set_new(info, "type", json_string("subscriber"));
2519 janus_videoroom_subscriber *participant = (janus_videoroom_subscriber *)session->participant;
2520 if(participant && participant->room) {
2521 janus_videoroom_publisher *feed = (janus_videoroom_publisher *)participant->feed;
2522 if(feed && feed->room) {
2523 janus_videoroom *room = feed->room;
2524 json_object_set_new(info, "room", room ? json_integer(room->room_id) : NULL);
2525 json_object_set_new(info, "private_id", json_integer(participant->pvt_id));
2526 json_object_set_new(info, "feed_id", json_integer(feed->user_id));
2527 if(feed->display)
2528 json_object_set_new(info, "feed_display", json_string(feed->display));
2529 }
2530 json_t *media = json_object();
2531 json_object_set_new(media, "audio", participant->audio ? json_true() : json_false());
2532 json_object_set_new(media, "audio-offered", participant->audio_offered ? json_true() : json_false());
2533 json_object_set_new(media, "video", participant->video ? json_true() : json_false());
2534 json_object_set_new(media, "video-offered", participant->video_offered ? json_true() : json_false());
2535 json_object_set_new(media, "data", participant->data ? json_true() : json_false());
2536 json_object_set_new(media, "data-offered", participant->data_offered ? json_true() : json_false());
2537 json_object_set_new(info, "media", media);
2538 if(feed && (feed->ssrc[0] != 0 || feed->rid[0] != NULL)) {
2539 json_t *simulcast = json_object();
2540 json_object_set_new(simulcast, "substream", json_integer(participant->sim_context.substream));
2541 json_object_set_new(simulcast, "substream-target", json_integer(participant->sim_context.substream_target));
2542 json_object_set_new(simulcast, "temporal-layer", json_integer(participant->sim_context.templayer));
2543 json_object_set_new(simulcast, "temporal-layer-target", json_integer(participant->sim_context.templayer_target));
2544 json_object_set_new(info, "simulcast", simulcast);
2545 }
2546 if(participant->room && participant->room->do_svc) {
2547 json_t *svc = json_object();
2548 json_object_set_new(svc, "spatial-layer", json_integer(participant->spatial_layer));
2549 json_object_set_new(svc, "target-spatial-layer", json_integer(participant->target_spatial_layer));
2550 json_object_set_new(svc, "temporal-layer", json_integer(participant->temporal_layer));
2551 json_object_set_new(svc, "target-temporal-layer", json_integer(participant->target_temporal_layer));
2552 json_object_set_new(info, "svc", svc);
2553 }
2554 }
2555 }
2556 }
2557 json_object_set_new(info, "hangingup", json_integer(g_atomic_int_get(&session->hangingup)));
2558 json_object_set_new(info, "destroyed", json_integer(g_atomic_int_get(&session->destroyed)));
2559 janus_refcount_decrease(&session->ref);
2560 return info;
2561}
2562
2563static int janus_videoroom_access_room(json_t *root, gboolean check_modify, gboolean check_join, janus_videoroom **videoroom, char *error_cause, int error_cause_size) {
2564 /* rooms_mutex has to be locked */
2565 int error_code = 0;
2566 json_t *room = json_object_get(root, "room");
2567 guint64 room_id = json_integer_value(room);
2568 *videoroom = g_hash_table_lookup(rooms, &room_id);
2569 if(*videoroom == NULL) {
2570 JANUS_LOG(LOG_ERR, "No such room (%"SCNu64")\n", room_id);
2571 error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM;
2572 if(error_cause)
2573 g_snprintf(error_cause, error_cause_size, "No such room (%"SCNu64")", room_id);
2574 return error_code;
2575 }
2576 if((*videoroom)->destroyed) {
2577 JANUS_LOG(LOG_ERR, "No such room (%"SCNu64")\n", room_id);
2578 error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM;
2579 if(error_cause)
2580 g_snprintf(error_cause, error_cause_size, "No such room (%"SCNu64")", room_id);
2581 return error_code;
2582 }
2583 if(check_modify) {
2584 char error_cause2[100];
2585 JANUS_CHECK_SECRET((*videoroom)->room_secret, root, "secret", error_code, error_cause2,
2586 JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT, JANUS_VIDEOROOM_ERROR_UNAUTHORIZED);
2587 if(error_code != 0) {
2588 g_strlcpy(error_cause, error_cause2, error_cause_size);
2589 return error_code;
2590 }
2591 }
2592 if(check_join) {
2593 char error_cause2[100];
2594 /* signed tokens bypass pin validation */
2595 json_t *token = json_object_get(root, "token");
2596 if(token) {
2597 char room_descriptor[26];
2598 g_snprintf(room_descriptor, sizeof(room_descriptor), "room=%"SCNu64, room_id);
2599 if(gateway->auth_signature_contains(&janus_videoroom_plugin, json_string_value(token), room_descriptor))
2600 return 0;
2601 }
2602 JANUS_CHECK_SECRET((*videoroom)->room_pin, root, "pin", error_code, error_cause2,
2603 JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT, JANUS_VIDEOROOM_ERROR_UNAUTHORIZED);
2604 if(error_code != 0) {
2605 g_strlcpy(error_cause, error_cause2, error_cause_size);
2606 return error_code;
2607 }
2608 }
2609 return 0;
2610}
2611
2612/* Helper method to process synchronous requests */
2613static json_t *janus_videoroom_process_synchronous_request(janus_videoroom_session *session, json_t *message) {
2614 json_t *request = json_object_get(message, "request");
2615 const char *request_text = json_string_value(request);
2616
2617 /* Parse the message */
2618 int error_code = 0;
2619 char error_cause[512];
2620 json_t *root = message;
2621 json_t *response = NULL;
2622
2623 if(!strcasecmp(request_text, "create")) {
2624 /* Create a new videoroom */
2625 JANUS_LOG(LOG_VERB, "Creating a new videoroom\n");
2626 JANUS_VALIDATE_JSON_OBJECT(root, create_parameters,
2627 error_code, error_cause, TRUE,
2628 JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT);
2629 if(error_code != 0)
2630 goto prepare_response;
2631 if(admin_key != NULL) {
2632 /* An admin key was specified: make sure it was provided, and that it's valid */
2633 JANUS_VALIDATE_JSON_OBJECT(root, adminkey_parameters,
2634 error_code, error_cause, TRUE,
2635 JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT);
2636 if(error_code != 0)
2637 goto prepare_response;
2638 JANUS_CHECK_SECRET(admin_key, root, "admin_key", error_code, error_cause,
2639 JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT, JANUS_VIDEOROOM_ERROR_UNAUTHORIZED);
2640 if(error_code != 0)
2641 goto prepare_response;
2642 }
2643 json_t *desc = json_object_get(root, "description");
2644 json_t *is_private = json_object_get(root, "is_private");
2645 json_t *req_pvtid = json_object_get(root, "require_pvtid");
2646 json_t *secret = json_object_get(root, "secret");
2647 json_t *pin = json_object_get(root, "pin");
2648 json_t *bitrate = json_object_get(root, "bitrate");
2649 json_t *bitrate_cap = json_object_get(root, "bitrate_cap");
2650 json_t *fir_freq = json_object_get(root, "fir_freq");
2651 json_t *publishers = json_object_get(root, "publishers");
2652 json_t *allowed = json_object_get(root, "allowed");
2653 json_t *audiocodec = json_object_get(root, "audiocodec");
2654 if(audiocodec) {
2655 const char *audiocodec_value = json_string_value(audiocodec);
2656 gchar **list = g_strsplit(audiocodec_value, ",", 4);
2657 gchar *codec = list[0];
2658 if(codec != NULL) {
2659 int i=0;
2660 while(codec != NULL) {
2661 if(i == 3) {
2662 break;
2663 }
2664 if(strlen(codec) == 0 || JANUS_AUDIOCODEC_NONE == janus_audiocodec_from_name(codec)) {
2665 JANUS_LOG(LOG_ERR, "Invalid element (audiocodec can only be or contain opus, isac32, isac16, pcmu, pcma or g722)\n");
2666 error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
2667 g_snprintf(error_cause, 512, "Invalid element (audiocodec can only be or contain opus, isac32, isac16, pcmu, pcma or g722)");
2668 goto prepare_response;
2669 }
2670 i++;
2671 codec = list[i];
2672 }
2673 }
2674 g_clear_pointer(&list, g_strfreev);
2675 }
2676 json_t *videocodec = json_object_get(root, "videocodec");
2677 if(videocodec) {
2678 const char *videocodec_value = json_string_value(videocodec);
2679 gchar **list = g_strsplit(videocodec_value, ",", 4);
2680 gchar *codec = list[0];
2681 if(codec != NULL) {
2682 int i=0;
2683 while(codec != NULL) {
2684 if(i == 3) {
2685 break;
2686 }
2687 if(strlen(codec) == 0 || JANUS_VIDEOCODEC_NONE == janus_videocodec_from_name(codec)) {
2688 JANUS_LOG(LOG_ERR, "Invalid element (videocodec can only be or contain vp8, vp9 or h264)\n");
2689 error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
2690 g_snprintf(error_cause, 512, "Invalid element (videocodec can only be or contain vp8, vp9 or h264)");
2691 goto prepare_response;
2692 }
2693 i++;
2694 codec = list[i];
2695 }
2696 }
2697 g_clear_pointer(&list, g_strfreev);
2698 }
2699 json_t *fec = json_object_get(root, "opus_fec");
2700 json_t *svc = json_object_get(root, "video_svc");
2701 json_t *audiolevel_ext = json_object_get(root, "audiolevel_ext");
2702 json_t *audiolevel_event = json_object_get(root, "audiolevel_event");
2703 json_t *audio_active_packets = json_object_get(root, "audio_active_packets");
2704 json_t *audio_level_average = json_object_get(root, "audio_level_average");
2705 json_t *videoorient_ext = json_object_get(root, "videoorient_ext");
2706 json_t *playoutdelay_ext = json_object_get(root, "playoutdelay_ext");
2707 json_t *transport_wide_cc_ext = json_object_get(root, "transport_wide_cc_ext");
2708 json_t *notify_joining = json_object_get(root, "notify_joining");
2709 json_t *record = json_object_get(root, "record");
2710 json_t *rec_dir = json_object_get(root, "rec_dir");
2711 json_t *permanent = json_object_get(root, "permanent");
2712 if(allowed) {
2713 /* Make sure the "allowed" array only contains strings */
2714 gboolean ok = TRUE;
2715 if(json_array_size(allowed) > 0) {
2716 size_t i = 0;
2717 for(i=0; i<json_array_size(allowed); i++) {
2718 json_t *a = json_array_get(allowed, i);
2719 if(!a || !json_is_string(a)) {
2720 ok = FALSE;
2721 break;
2722 }
2723 }
2724 }
2725 if(!ok) {
2726 JANUS_LOG(LOG_ERR, "Invalid element in the allowed array (not a string)\n");
2727 error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
2728 g_snprintf(error_cause, 512, "Invalid element in the allowed array (not a string)");
2729 goto prepare_response;
2730 }
2731 }
2732 gboolean save = permanent ? json_is_true(permanent) : FALSE;
2733 if(save && config == NULL) {
2734 JANUS_LOG(LOG_ERR, "No configuration file, can't create permanent room\n");
2735 error_code = JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR;
2736 g_snprintf(error_cause, 512, "No configuration file, can't create permanent room");
2737 goto prepare_response;
2738 }
2739 guint64 room_id = 0;
2740 json_t *room = json_object_get(root, "room");
2741 if(room) {
2742 room_id = json_integer_value(room);
2743 if(room_id == 0) {
2744 JANUS_LOG(LOG_WARN, "Desired room ID is 0, which is not allowed... picking random ID instead\n");
2745 }
2746 }
2747 janus_mutex_lock(&rooms_mutex);
2748 if(room_id > 0) {
2749 /* Let's make sure the room doesn't exist already */
2750 if(g_hash_table_lookup(rooms, &room_id) != NULL) {
2751 /* It does... */
2752 janus_mutex_unlock(&rooms_mutex);
2753 JANUS_LOG(LOG_ERR, "Room %"SCNu64" already exists!\n", room_id);
2754 error_code = JANUS_VIDEOROOM_ERROR_ROOM_EXISTS;
2755 g_snprintf(error_cause, 512, "Room %"SCNu64" already exists", room_id);
2756 goto prepare_response;
2757 }
2758 }
2759 /* Create the room */
2760 janus_videoroom *videoroom = g_malloc0(sizeof(janus_videoroom));
2761 /* Generate a random ID */
2762 if(room_id == 0) {
2763 while(room_id == 0) {
2764 room_id = janus_random_uint64();
2765 if(g_hash_table_lookup(rooms, &room_id) != NULL) {
2766 /* Room ID already taken, try another one */
2767 room_id = 0;
2768 }
2769 }
2770 }
2771 videoroom->room_id = room_id;
2772 char *description = NULL;
2773 if(desc != NULL && strlen(json_string_value(desc)) > 0) {
2774 description = g_strdup(json_string_value(desc));
2775 } else {
2776 char roomname[255];
2777 g_snprintf(roomname, 255, "Room %"SCNu64"", videoroom->room_id);
2778 description = g_strdup(roomname);
2779 }
2780 videoroom->room_name = description;
2781 videoroom->is_private = is_private ? json_is_true(is_private) : FALSE;
2782 videoroom->require_pvtid = req_pvtid ? json_is_true(req_pvtid) : FALSE;
2783 if(secret)
2784 videoroom->room_secret = g_strdup(json_string_value(secret));
2785 if(pin)
2786 videoroom->room_pin = g_strdup(json_string_value(pin));
2787 videoroom->max_publishers = 3; /* FIXME How should we choose a default? */
2788 if(publishers)
2789 videoroom->max_publishers = json_integer_value(publishers);
2790 if(videoroom->max_publishers < 0)
2791 videoroom->max_publishers = 3; /* FIXME How should we choose a default? */
2792 videoroom->bitrate = 0;
2793 if(bitrate)
2794 videoroom->bitrate = json_integer_value(bitrate);
2795 if(videoroom->bitrate > 0 && videoroom->bitrate < 64000)
2796 videoroom->bitrate = 64000; /* Don't go below 64k */
2797 videoroom->bitrate_cap = bitrate_cap ? json_is_true(bitrate_cap) : FALSE;
2798 videoroom->fir_freq = 0;
2799 if(fir_freq)
2800 videoroom->fir_freq = json_integer_value(fir_freq);
2801 /* By default, we force Opus as the only audio codec */
2802 videoroom->acodec[0] = JANUS_AUDIOCODEC_OPUS;
2803 videoroom->acodec[1] = JANUS_AUDIOCODEC_NONE;
2804 videoroom->acodec[2] = JANUS_AUDIOCODEC_NONE;
2805 /* Check if we're forcing a different single codec, or allowing more than one */
2806 if(audiocodec) {
2807 const char *audiocodec_value = json_string_value(audiocodec);
2808 gchar **list = g_strsplit(audiocodec_value, ",", 4);
2809 gchar *codec = list[0];
2810 if(codec != NULL) {
2811 int i=0;
2812 while(codec != NULL) {
2813 if(i == 3) {
2814 JANUS_LOG(LOG_WARN, "Ignoring extra audio codecs: %s\n", codec);
2815 break;
2816 }
2817 if(strlen(codec) > 0)
2818 videoroom->acodec[i] = janus_audiocodec_from_name(codec);
2819 i++;
2820 codec = list[i];
2821 }
2822 }
2823 g_clear_pointer(&list, g_strfreev);
2824 }
2825 /* By default, we force VP8 as the only video codec */
2826 videoroom->vcodec[0] = JANUS_VIDEOCODEC_VP8;
2827 videoroom->vcodec[1] = JANUS_VIDEOCODEC_NONE;
2828 videoroom->vcodec[2] = JANUS_VIDEOCODEC_NONE;
2829 /* Check if we're forcing a different single codec, or allowing more than one */
2830 if(videocodec) {
2831 const char *videocodec_value = json_string_value(videocodec);
2832 gchar **list = g_strsplit(videocodec_value, ",", 4);
2833 gchar *codec = list[0];
2834 if(codec != NULL) {
2835 int i=0;
2836 while(codec != NULL) {
2837 if(i == 3) {
2838 JANUS_LOG(LOG_WARN, "Ignoring extra video codecs: %s\n", codec);
2839 break;
2840 }
2841 if(strlen(codec) > 0)
2842 videoroom->vcodec[i] = janus_videocodec_from_name(codec);
2843 i++;
2844 codec = list[i];
2845 }
2846 }
2847 g_clear_pointer(&list, g_strfreev);
2848 }
2849 if(fec) {
2850 videoroom->do_opusfec = json_is_true(fec);
2851 if(videoroom->acodec[0] != JANUS_AUDIOCODEC_OPUS &&
2852 videoroom->acodec[1] != JANUS_AUDIOCODEC_OPUS &&
2853 videoroom->acodec[2] != JANUS_AUDIOCODEC_OPUS) {
2854 videoroom->do_opusfec = FALSE;
2855 JANUS_LOG(LOG_WARN, "Inband FEC is only supported for rooms that allow Opus: disabling it...\n");
2856 }
2857 }
2858 if(svc && json_is_true(svc)) {
2859 if(videoroom->vcodec[0] == JANUS_VIDEOCODEC_VP9 &&
2860 videoroom->vcodec[1] == JANUS_VIDEOCODEC_NONE &&
2861 videoroom->vcodec[2] == JANUS_VIDEOCODEC_NONE) {
2862 videoroom->do_svc = TRUE;
2863 } else {
2864 JANUS_LOG(LOG_WARN, "SVC is only supported, in an experimental way, for VP9 only rooms: disabling it...\n");
2865 }
2866 }
2867 videoroom->audiolevel_ext = audiolevel_ext ? json_is_true(audiolevel_ext) : TRUE;
2868 videoroom->audiolevel_event = audiolevel_event ? json_is_true(audiolevel_event) : FALSE;
2869 if(videoroom->audiolevel_event) {
2870 videoroom->audio_active_packets = 100;
2871 if(json_integer_value(audio_active_packets) > 0) {
2872 videoroom->audio_active_packets = json_integer_value(audio_active_packets);
2873 } else {
2874 JANUS_LOG(LOG_WARN, "Invalid audio_active_packets value provided, using default: %d\n", videoroom->audio_active_packets);
2875 }
2876 videoroom->audio_level_average = 25;
2877 if(json_integer_value(audio_level_average) > 0) {
2878 videoroom->audio_level_average = json_integer_value(audio_level_average);
2879 } else {
2880 JANUS_LOG(LOG_WARN, "Invalid audio_level_average value provided, using default: %d\n", videoroom->audio_level_average);
2881 }
2882 }
2883 videoroom->videoorient_ext = videoorient_ext ? json_is_true(videoorient_ext) : TRUE;
2884 videoroom->playoutdelay_ext = playoutdelay_ext ? json_is_true(playoutdelay_ext) : TRUE;
2885 videoroom->transport_wide_cc_ext = transport_wide_cc_ext ? json_is_true(transport_wide_cc_ext) : TRUE;
2886 /* By default, the videoroom plugin does not notify about participants simply joining the room.
2887 It only notifies when the participant actually starts publishing media. */
2888 videoroom->notify_joining = notify_joining ? json_is_true(notify_joining) : FALSE;
2889 if(record) {
2890 videoroom->record = json_is_true(record);
2891 }
2892 if(rec_dir) {
2893 videoroom->rec_dir = g_strdup(json_string_value(rec_dir));
2894 }
2895 g_atomic_int_set(&videoroom->destroyed, 0);
2896 janus_mutex_init(&videoroom->mutex);
2897 janus_refcount_init(&videoroom->ref, janus_videoroom_room_free);
2898 videoroom->participants = g_hash_table_new_full(g_int64_hash, g_int64_equal, (GDestroyNotify)g_free, (GDestroyNotify)janus_videoroom_publisher_dereference);
2899 videoroom->private_ids = g_hash_table_new(NULL, NULL);
2900 videoroom->allowed = g_hash_table_new_full(g_str_hash, g_str_equal, (GDestroyNotify)g_free, NULL);
2901 if(allowed != NULL) {
2902 /* Populate the "allowed" list as an ACL for people trying to join */
2903 if(json_array_size(allowed) > 0) {
2904 size_t i = 0;
2905 for(i=0; i<json_array_size(allowed); i++) {
2906 const char *token = json_string_value(json_array_get(allowed, i));
2907 if(!g_hash_table_lookup(videoroom->allowed, token))
2908 g_hash_table_insert(videoroom->allowed, g_strdup(token), GINT_TO_POINTER(TRUE));
2909 }
2910 }
2911 videoroom->check_allowed = TRUE;
2912 }
2913 /* Compute a list of the supported codecs for the summary */
2914 char audio_codecs[100], video_codecs[100];
2915 janus_videoroom_codecstr(videoroom, audio_codecs, video_codecs, sizeof(audio_codecs), "|");
2916 JANUS_LOG(LOG_VERB, "Created videoroom: %"SCNu64" (%s, %s, %s/%s codecs, secret: %s, pin: %s, pvtid: %s)\n",
2917 videoroom->room_id, videoroom->room_name,
2918 videoroom->is_private ? "private" : "public",
2919 audio_codecs, video_codecs,
2920 videoroom->room_secret ? videoroom->room_secret : "no secret",
2921 videoroom->room_pin ? videoroom->room_pin : "no pin",
2922 videoroom->require_pvtid ? "required" : "optional");
2923 if(videoroom->record) {
2924 JANUS_LOG(LOG_VERB, " -- Room is going to be recorded in %s\n", videoroom->rec_dir ? videoroom->rec_dir : "the current folder");
2925 }
2926 if(save) {
2927 /* This room is permanent: save to the configuration file too
2928 * FIXME: We should check if anything fails... */
2929 JANUS_LOG(LOG_VERB, "Saving room %"SCNu64" permanently in config file\n", videoroom->room_id);
2930 janus_mutex_lock(&config_mutex);
2931 char cat[BUFSIZ], value[BUFSIZ];
2932 /* The room ID is the category (prefixed by "room-") */
2933 g_snprintf(cat, BUFSIZ, "room-%"SCNu64, videoroom->room_id);
2934 janus_config_category *c = janus_config_get_create(config, NULL, janus_config_type_category, cat);
2935 /* Now for the values */
2936 janus_config_add(config, c, janus_config_item_create("description", videoroom->room_name));
2937 if(videoroom->is_private)
2938 janus_config_add(config, c, janus_config_item_create("is_private", "yes"));
2939 if(videoroom->require_pvtid)
2940 janus_config_add(config, c, janus_config_item_create("require_pvtid", "yes"));
2941 g_snprintf(value, BUFSIZ, "%"SCNu32, videoroom->bitrate);
2942 janus_config_add(config, c, janus_config_item_create("bitrate", value));
2943 if(videoroom->bitrate_cap)
2944 janus_config_add(config, c, janus_config_item_create("bitrate_cap", "yes"));
2945 g_snprintf(value, BUFSIZ, "%d", videoroom->max_publishers);
2946 janus_config_add(config, c, janus_config_item_create("publishers", value));
2947 if(videoroom->fir_freq) {
2948 g_snprintf(value, BUFSIZ, "%"SCNu16, videoroom->fir_freq);
2949 janus_config_add(config, c, janus_config_item_create("fir_freq", value));
2950 }
2951 char video_codecs[100];
2952 char audio_codecs[100];
2953 janus_videoroom_codecstr(videoroom, audio_codecs, video_codecs, sizeof(audio_codecs), ",");
2954 janus_config_add(config, c, janus_config_item_create("audiocodec", audio_codecs));
2955 janus_config_add(config, c, janus_config_item_create("videocodec", video_codecs));
2956 if(videoroom->do_opusfec)
2957 janus_config_add(config, c, janus_config_item_create("opus_fec", "yes"));
2958 if(videoroom->do_svc)
2959 janus_config_add(config, c, janus_config_item_create("video_svc", "yes"));
2960 if(videoroom->room_secret)
2961 janus_config_add(config, c, janus_config_item_create("secret", videoroom->room_secret));
2962 if(videoroom->room_pin)
2963 janus_config_add(config, c, janus_config_item_create("pin", videoroom->room_pin));
2964 if(videoroom->audiolevel_ext) {
2965 janus_config_add(config, c, janus_config_item_create("audiolevel_ext", "yes"));
2966 if(videoroom->audiolevel_event)
2967 janus_config_add(config, c, janus_config_item_create("audiolevel_event", "yes"));
2968 if(videoroom->audio_active_packets > 0) {
2969 g_snprintf(value, BUFSIZ, "%d", videoroom->audio_active_packets);
2970 janus_config_add(config, c, janus_config_item_create("audio_active_packets", value));
2971 }
2972 if(videoroom->audio_level_average > 0) {
2973 g_snprintf(value, BUFSIZ, "%d", videoroom->audio_level_average);
2974 janus_config_add(config, c, janus_config_item_create("audio_level_average", value));
2975 }
2976 } else {
2977 janus_config_add(config, c, janus_config_item_create("audiolevel_ext", "no"));
2978 }
2979 janus_config_add(config, c, janus_config_item_create("videoorient_ext", videoroom->videoorient_ext ? "yes" : "no"));
2980 janus_config_add(config, c, janus_config_item_create("playoutdelay_ext", videoroom->playoutdelay_ext ? "yes" : "no"));
2981 janus_config_add(config, c, janus_config_item_create("transport_wide_cc_ext", videoroom->transport_wide_cc_ext ? "yes" : "no"));
2982 if(videoroom->notify_joining)
2983 janus_config_add(config, c, janus_config_item_create("notify_joining", "yes"));
2984 if(videoroom->record)
2985 janus_config_add(config, c, janus_config_item_create("record", "yes"));
2986 if(videoroom->rec_dir)
2987 janus_config_add(config, c, janus_config_item_create("rec_dir", videoroom->rec_dir));
2988 /* Save modified configuration */
2989 if(janus_config_save(config, config_folder, JANUS_VIDEOROOM_PACKAGE) < 0)
2990 save = FALSE; /* This will notify the user the room is not permanent */
2991 janus_mutex_unlock(&config_mutex);
2992 }
2993
2994 g_hash_table_insert(rooms, janus_uint64_dup(videoroom->room_id), videoroom);
2995 /* Show updated rooms list */
2996 GHashTableIter iter;
2997 gpointer value;
2998 g_hash_table_iter_init(&iter, rooms);
2999 while (g_hash_table_iter_next(&iter, NULL, &value)) {
3000 janus_videoroom *vr = value;
3001 JANUS_LOG(LOG_VERB, " ::: [%"SCNu64"][%s] %"SCNu32", max %d publishers, FIR frequency of %d seconds\n", vr->room_id, vr->room_name, vr->bitrate, vr->max_publishers, vr->fir_freq);
3002 }
3003 janus_mutex_unlock(&rooms_mutex);
3004 /* Send info back */
3005 response = json_object();
3006 json_object_set_new(response, "videoroom", json_string("created"));
3007 json_object_set_new(response, "room", json_integer(videoroom->room_id));
3008 json_object_set_new(response, "permanent", save ? json_true() : json_false());
3009 /* Also notify event handlers */
3010 if(notify_events && gateway->events_is_enabled()) {
3011 json_t *info = json_object();
3012 json_object_set_new(info, "event", json_string("created"));
3013 json_object_set_new(info, "room", json_integer(videoroom->room_id));
3014 gateway->notify_event(&janus_videoroom_plugin, session ? session->handle : NULL, info);
3015 }
3016 goto prepare_response;
3017 } else if(!strcasecmp(request_text, "edit")) {
3018 /* Edit the properties for an existing videoroom */
3019 JANUS_LOG(LOG_VERB, "Attempt to edit the properties of an existing videoroom room\n");
3020 JANUS_VALIDATE_JSON_OBJECT(root, edit_parameters,
3021 error_code, error_cause, TRUE,
3022 JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT);
3023 if(error_code != 0)
3024 goto prepare_response;
3025 /* We only allow for a limited set of properties to be edited */
3026 json_t *desc = json_object_get(root, "new_description");
3027 json_t *is_private = json_object_get(root, "new_is_private");
3028 json_t *req_pvtid = json_object_get(root, "new_require_pvtid");
3029 json_t *secret = json_object_get(root, "new_secret");
3030 json_t *pin = json_object_get(root, "new_pin");
3031 json_t *bitrate = json_object_get(root, "new_bitrate");
3032 json_t *fir_freq = json_object_get(root, "new_fir_freq");
3033 json_t *publishers = json_object_get(root, "new_publishers");
3034 json_t *permanent = json_object_get(root, "permanent");
3035 gboolean save = permanent ? json_is_true(permanent) : FALSE;
3036 if(save && config == NULL) {
3037 JANUS_LOG(LOG_ERR, "No configuration file, can't edit room permanently\n");
3038 error_code = JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR;
3039 g_snprintf(error_cause, 512, "No configuration file, can't edit room permanently");
3040 goto prepare_response;
3041 }
3042 janus_mutex_lock(&rooms_mutex);
3043 janus_videoroom *videoroom = NULL;
3044 error_code = janus_videoroom_access_room(root, TRUE, FALSE, &videoroom, error_cause, sizeof(error_cause));
3045 if(error_code != 0) {
3046 janus_mutex_unlock(&rooms_mutex);
3047 goto prepare_response;
3048 }
3049 /* Edit the room properties that were provided */
3050 if(desc != NULL && strlen(json_string_value(desc)) > 0) {
3051 char *old_description = videoroom->room_name;
3052 char *new_description = g_strdup(json_string_value(desc));
3053 videoroom->room_name = new_description;
3054 g_free(old_description);
3055 }
3056 if(is_private)
3057 videoroom->is_private = json_is_true(is_private);
3058 if(req_pvtid)
3059 videoroom->require_pvtid = json_is_true(req_pvtid);
3060 if(publishers)
3061 videoroom->max_publishers = json_integer_value(publishers);
3062 if(bitrate) {
3063 videoroom->bitrate = json_integer_value(bitrate);
3064 if(videoroom->bitrate > 0 && videoroom->bitrate < 64000)
3065 videoroom->bitrate = 64000; /* Don't go below 64k */
3066 }
3067 if(fir_freq)
3068 videoroom->fir_freq = json_integer_value(fir_freq);
3069 if(secret && strlen(json_string_value(secret)) > 0) {
3070 char *old_secret = videoroom->room_secret;
3071 char *new_secret = g_strdup(json_string_value(secret));
3072 videoroom->room_secret = new_secret;
3073 g_free(old_secret);
3074 }
3075 if(pin && strlen(json_string_value(pin)) > 0) {
3076 char *old_pin = videoroom->room_pin;
3077 char *new_pin = g_strdup(json_string_value(pin));
3078 videoroom->room_pin = new_pin;
3079 g_free(old_pin);
3080 }
3081 if(save) {
3082 /* This room is permanent: save to the configuration file too
3083 * FIXME: We should check if anything fails... */
3084 JANUS_LOG(LOG_VERB, "Modifying room %"SCNu64" permanently in config file\n", videoroom->room_id);
3085 janus_mutex_lock(&config_mutex);
3086 char cat[BUFSIZ], value[BUFSIZ];
3087 /* The room ID is the category (prefixed by "room-") */
3088 g_snprintf(cat, BUFSIZ, "room-%"SCNu64, videoroom->room_id);
3089 /* Remove the old category first */
3090 janus_config_remove(config, NULL, cat);
3091 /* Now write the room details again */
3092 janus_config_category *c = janus_config_get_create(config, NULL, janus_config_type_category, cat);
3093 janus_config_add(config, c, janus_config_item_create("description", videoroom->room_name));
3094 if(videoroom->is_private)
3095 janus_config_add(config, c, janus_config_item_create("is_private", "yes"));
3096 if(videoroom->require_pvtid)
3097 janus_config_add(config, c, janus_config_item_create("require_pvtid", "yes"));
3098 g_snprintf(value, BUFSIZ, "%"SCNu32, videoroom->bitrate);
3099 janus_config_add(config, c, janus_config_item_create("bitrate", value));
3100 if(videoroom->bitrate_cap)
3101 janus_config_add(config, c, janus_config_item_create("bitrate_cap", "yes"));
3102 g_snprintf(value, BUFSIZ, "%d", videoroom->max_publishers);
3103 janus_config_add(config, c, janus_config_item_create("publishers", value));
3104 if(videoroom->fir_freq) {
3105 g_snprintf(value, BUFSIZ, "%"SCNu16, videoroom->fir_freq);
3106 janus_config_add(config, c, janus_config_item_create("fir_freq", value));
3107 }
3108 char audio_codecs[100];
3109 char video_codecs[100];
3110 janus_videoroom_codecstr(videoroom, audio_codecs, video_codecs, sizeof(audio_codecs), ",");
3111 janus_config_add(config, c, janus_config_item_create("audiocodec", audio_codecs));
3112 janus_config_add(config, c, janus_config_item_create("videocodec", video_codecs));
3113 if(videoroom->do_opusfec)
3114 janus_config_add(config, c, janus_config_item_create("opus_fec", "yes"));
3115 if(videoroom->do_svc)
3116 janus_config_add(config, c, janus_config_item_create("video_svc", "yes"));
3117 if(videoroom->room_secret)
3118 janus_config_add(config, c, janus_config_item_create("secret", videoroom->room_secret));
3119 if(videoroom->room_pin)
3120 janus_config_add(config, c, janus_config_item_create("pin", videoroom->room_pin));
3121 if(videoroom->audiolevel_ext) {
3122 janus_config_add(config, c, janus_config_item_create("audiolevel_ext", "yes"));
3123 if(videoroom->audiolevel_event)
3124 janus_config_add(config, c, janus_config_item_create("audiolevel_event", "yes"));
3125 if(videoroom->audio_active_packets > 0) {
3126 g_snprintf(value, BUFSIZ, "%d", videoroom->audio_active_packets);
3127 janus_config_add(config, c, janus_config_item_create("audio_active_packets", value));
3128 }
3129 if(videoroom->audio_level_average > 0) {
3130 g_snprintf(value, BUFSIZ, "%d", videoroom->audio_level_average);
3131 janus_config_add(config, c, janus_config_item_create("audio_level_average", value));
3132 }
3133 } else {
3134 janus_config_add(config, c, janus_config_item_create("audiolevel_ext", "no"));
3135 }
3136 janus_config_add(config, c, janus_config_item_create("videoorient_ext", videoroom->videoorient_ext ? "yes" : "no"));
3137 janus_config_add(config, c, janus_config_item_create("playoutdelay_ext", videoroom->playoutdelay_ext ? "yes" : "no"));
3138 janus_config_add(config, c, janus_config_item_create("transport_wide_cc_ext", videoroom->transport_wide_cc_ext ? "yes" : "no"));
3139 if(videoroom->notify_joining)
3140 janus_config_add(config, c, janus_config_item_create("notify_joining", "yes"));
3141 if(videoroom->record)
3142 janus_config_add(config, c, janus_config_item_create("record", "yes"));
3143 if(videoroom->rec_dir)
3144 janus_config_add(config, c, janus_config_item_create("rec_dir", videoroom->rec_dir));
3145 /* Save modified configuration */
3146 if(janus_config_save(config, config_folder, JANUS_VIDEOROOM_PACKAGE) < 0)
3147 save = FALSE; /* This will notify the user the room changes are not permanent */
3148 janus_mutex_unlock(&config_mutex);
3149 }
3150 janus_mutex_unlock(&rooms_mutex);
3151 /* Send info back */
3152 response = json_object();
3153 json_object_set_new(response, "videoroom", json_string("edited"));
3154 json_object_set_new(response, "room", json_integer(videoroom->room_id));
3155 json_object_set_new(response, "permanent", save ? json_true() : json_false());
3156 /* Also notify event handlers */
3157 if(notify_events && gateway->events_is_enabled()) {
3158 json_t *info = json_object();
3159 json_object_set_new(info, "event", json_string("edited"));
3160 json_object_set_new(info, "room", json_integer(videoroom->room_id));
3161 gateway->notify_event(&janus_videoroom_plugin, session ? session->handle : NULL, info);
3162 }
3163 goto prepare_response;
3164 } else if(!strcasecmp(request_text, "destroy")) {
3165 JANUS_LOG(LOG_VERB, "Attempt to destroy an existing videoroom room\n");
3166 JANUS_VALIDATE_JSON_OBJECT(root, destroy_parameters,
3167 error_code, error_cause, TRUE,
3168 JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT);
3169 if(error_code != 0)
3170 goto prepare_response;
3171 json_t *room = json_object_get(root, "room");
3172 json_t *permanent = json_object_get(root, "permanent");
3173 gboolean save = permanent ? json_is_true(permanent) : FALSE;
3174 if(save && config == NULL) {
3175 JANUS_LOG(LOG_ERR, "No configuration file, can't destroy room permanently\n");
3176 error_code = JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR;
3177 g_snprintf(error_cause, 512, "No configuration file, can't destroy room permanently");
3178 goto prepare_response;
3179 }
3180 guint64 room_id = json_integer_value(room);
3181 janus_mutex_lock(&rooms_mutex);
3182 janus_videoroom *videoroom = NULL;
3183 error_code = janus_videoroom_access_room(root, TRUE, FALSE, &videoroom, error_cause, sizeof(error_cause));
3184 if(error_code != 0) {
3185 janus_mutex_unlock(&rooms_mutex);
3186 goto prepare_response;
3187 }
3188 /* Remove room, but add a reference until we're done */
3189 janus_refcount_increase(&videoroom->ref);
3190 g_hash_table_remove(rooms, &room_id);
3191 /* Notify all participants that the fun is over, and that they'll be kicked */
3192 JANUS_LOG(LOG_VERB, "Notifying all participants\n");
3193 json_t *destroyed = json_object();
3194 json_object_set_new(destroyed, "videoroom", json_string("destroyed"));
3195 json_object_set_new(destroyed, "room", json_integer(room_id));
3196 GHashTableIter iter;
3197 gpointer value;
3198 janus_mutex_lock(&videoroom->mutex);
3199 g_hash_table_iter_init(&iter, videoroom->participants);
3200 while (g_hash_table_iter_next(&iter, NULL, &value)) {
3201 janus_videoroom_publisher *p = value;
3202 if(p && p->session) {
3203 g_clear_pointer(&p->room, janus_videoroom_room_dereference);
3204 /* Notify the user we're going to destroy the room... */
3205 int ret = gateway->push_event(p->session->handle, &janus_videoroom_plugin, NULL, destroyed, NULL);
3206 JANUS_LOG(LOG_VERB, " >> %d (%s)\n", ret, janus_get_api_error(ret));
3207 /* ... and then ask the core to close the PeerConnection */
3208 gateway->close_pc(p->session->handle);
3209 }
3210 }
3211 json_decref(destroyed);
3212 janus_mutex_unlock(&videoroom->mutex);
3213 /* Also notify event handlers */
3214 if(notify_events && gateway->events_is_enabled()) {
3215 json_t *info = json_object();
3216 json_object_set_new(info, "event", json_string("destroyed"));
3217 json_object_set_new(info, "room", json_integer(room_id));
3218 gateway->notify_event(&janus_videoroom_plugin, session ? session->handle : NULL, info);
3219 }
3220 janus_mutex_unlock(&rooms_mutex);
3221 if(save) {
3222 /* This change is permanent: save to the configuration file too
3223 * FIXME: We should check if anything fails... */
3224 JANUS_LOG(LOG_VERB, "Destroying room %"SCNu64" permanently in config file\n", room_id);
3225 janus_mutex_lock(&config_mutex);
3226 char cat[BUFSIZ];
3227 /* The room ID is the category (prefixed by "room-") */
3228 g_snprintf(cat, BUFSIZ, "room-%"SCNu64, room_id);
3229 janus_config_remove(config, NULL, cat);
3230 /* Save modified configuration */
3231 if(janus_config_save(config, config_folder, JANUS_VIDEOROOM_PACKAGE) < 0)
3232 save = FALSE; /* This will notify the user the room destruction is not permanent */
3233 janus_mutex_unlock(&config_mutex);
3234 }
3235 janus_refcount_decrease(&videoroom->ref);
3236 /* Done */
3237 response = json_object();
3238 json_object_set_new(response, "videoroom", json_string("destroyed"));
3239 json_object_set_new(response, "room", json_integer(room_id));
3240 json_object_set_new(response, "permanent", save ? json_true() : json_false());
3241 goto prepare_response;
3242 } else if(!strcasecmp(request_text, "list")) {
3243 /* List all rooms (but private ones) and their details (except for the secret, of course...) */
3244 json_t *list = json_array();
3245 JANUS_LOG(LOG_VERB, "Getting the list of video rooms\n");
3246 janus_mutex_lock(&rooms_mutex);
3247 GHashTableIter iter;
3248 gpointer value;
3249 g_hash_table_iter_init(&iter, rooms);
3250 while(g_hash_table_iter_next(&iter, NULL, &value)) {
3251 janus_videoroom *room = value;
3252 if(!room)
3253 continue;
3254 janus_refcount_increase(&room->ref);
3255 if(room->is_private) {
3256 /* Skip private room */
3257 JANUS_LOG(LOG_VERB, "Skipping private room '%s'\n", room->room_name);
3258 janus_refcount_decrease(&room->ref);
3259 continue;
3260 }
3261 if(!g_atomic_int_get(&room->destroyed)) {
3262 json_t *rl = json_object();
3263 json_object_set_new(rl, "room", json_integer(room->room_id));
3264 json_object_set_new(rl, "description", json_string(room->room_name));
3265 json_object_set_new(rl, "pin_required", room->room_pin ? json_true() : json_false());
3266 json_object_set_new(rl, "max_publishers", json_integer(room->max_publishers));
3267 json_object_set_new(rl, "bitrate", json_integer(room->bitrate));
3268 if(room->bitrate_cap)
3269 json_object_set_new(rl, "bitrate_cap", json_true());
3270 json_object_set_new(rl, "fir_freq", json_integer(room->fir_freq));
3271 json_object_set_new(rl, "require_pvtid", room->require_pvtid ? json_true() : json_false());
3272 json_object_set_new(rl, "notify_joining", room->notify_joining ? json_true() : json_false());
3273 char audio_codecs[100];
3274 char video_codecs[100];
3275 janus_videoroom_codecstr(room, audio_codecs, video_codecs, sizeof(audio_codecs), ",");
3276 json_object_set_new(rl, "audiocodec", json_string(audio_codecs));
3277 json_object_set_new(rl, "videocodec", json_string(video_codecs));
3278 if(room->do_opusfec)
3279 json_object_set_new(rl, "opus_fec", json_true());
3280 if(room->do_svc)
3281 json_object_set_new(rl, "video_svc", json_true());
3282 json_object_set_new(rl, "record", room->record ? json_true() : json_false());
3283 json_object_set_new(rl, "rec_dir", json_string(room->rec_dir));
3284 /* TODO: Should we list participants as well? or should there be a separate API call on a specific room for this? */
3285 json_object_set_new(rl, "num_participants", json_integer(g_hash_table_size(room->participants)));
3286 json_array_append_new(list, rl);
3287 }
3288 janus_refcount_decrease(&room->ref);
3289 }
3290 janus_mutex_unlock(&rooms_mutex);
3291 response = json_object();
3292 json_object_set_new(response, "videoroom", json_string("success"));
3293 json_object_set_new(response, "list", list);
3294 goto prepare_response;
3295 } else if(!strcasecmp(request_text, "rtp_forward")) {
3296 JANUS_VALIDATE_JSON_OBJECT(root, rtp_forward_parameters,
3297 error_code, error_cause, TRUE,
3298 JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT);
3299 if(error_code != 0)
3300 goto prepare_response;
3301 if(lock_rtpfwd && admin_key != NULL) {
3302 /* An admin key was specified: make sure it was provided, and that it's valid */
3303 JANUS_VALIDATE_JSON_OBJECT(root, adminkey_parameters,
3304 error_code, error_cause, TRUE,
3305 JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT);
3306 if(error_code != 0)
3307 goto prepare_response;
3308 JANUS_CHECK_SECRET(admin_key, root, "admin_key", error_code, error_cause,
3309 JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT, JANUS_VIDEOROOM_ERROR_UNAUTHORIZED);
3310 if(error_code != 0)
3311 goto prepare_response;
3312 }
3313 json_t *room = json_object_get(root, "room");
3314 json_t *pub_id = json_object_get(root, "publisher_id");
3315 int video_port[3] = {-1, -1, -1}, video_rtcp_port = -1, video_pt[3] = {0, 0, 0};
3316 uint32_t video_ssrc[3] = {0, 0, 0};
3317 int audio_port = -1, audio_rtcp_port = -1, audio_pt = 0;
3318 uint32_t audio_ssrc = 0;
3319 int data_port = -1;
3320 int srtp_suite = 0;
3321 const char *srtp_crypto = NULL;
3322 /* There may be multiple target video ports (e.g., publisher simulcasting) */
3323 json_t *vid_port = json_object_get(root, "video_port");
3324 if(vid_port) {
3325 video_port[0] = json_integer_value(vid_port);
3326 json_t *pt = json_object_get(root, "video_pt");
3327 if(pt)
3328 video_pt[0] = json_integer_value(pt);
3329 json_t *ssrc = json_object_get(root, "video_ssrc");
3330 if(ssrc)
3331 video_ssrc[0] = json_integer_value(ssrc);
3332 }
3333 vid_port = json_object_get(root, "video_port_2");
3334 if(vid_port) {
3335 video_port[1] = json_integer_value(vid_port);
3336 json_t *pt = json_object_get(root, "video_pt_2");
3337 if(pt)
3338 video_pt[1] = json_integer_value(pt);
3339 json_t *ssrc = json_object_get(root, "video_ssrc_2");
3340 if(ssrc)
3341 video_ssrc[1] = json_integer_value(ssrc);
3342 }
3343 vid_port = json_object_get(root, "video_port_3");
3344 if(vid_port) {
3345 video_port[2] = json_integer_value(vid_port);
3346 json_t *pt = json_object_get(root, "video_pt_3");
3347 if(pt)
3348 video_pt[2] = json_integer_value(pt);
3349 json_t *ssrc = json_object_get(root, "video_ssrc_3");
3350 if(ssrc)
3351 video_ssrc[2] = json_integer_value(ssrc);
3352 }
3353 json_t *vid_rtcp_port = json_object_get(root, "video_rtcp_port");
3354 if(vid_rtcp_port)
3355 video_rtcp_port = json_integer_value(vid_rtcp_port);
3356 /* Audio target */
3357 json_t *au_port = json_object_get(root, "audio_port");
3358 if(au_port) {
3359 audio_port = json_integer_value(au_port);
3360 json_t *pt = json_object_get(root, "audio_pt");
3361 if(pt)
3362 audio_pt = json_integer_value(pt);
3363 json_t *ssrc = json_object_get(root, "audio_ssrc");
3364 if(ssrc)
3365 audio_ssrc = json_integer_value(ssrc);
3366 }
3367 json_t *au_rtcp_port = json_object_get(root, "audio_rtcp_port");
3368 if(au_rtcp_port)
3369 audio_rtcp_port = json_integer_value(au_rtcp_port);
3370 /* Data target */
3371 json_t *d_port = json_object_get(root, "data_port");
3372 if(d_port) {
3373 data_port = json_integer_value(d_port);
3374 }
3375 json_t *json_host = json_object_get(root, "host");
3376 /* Do we need to forward multiple simulcast streams to a single endpoint? */
3377 gboolean simulcast = FALSE;
3378 if(json_object_get(root, "simulcast") != NULL)
3379 simulcast = json_is_true(json_object_get(root, "simulcast"));
3380 if(simulcast) {
3381 /* We do, disable the other video ports if they were requested */
3382 video_port[1] = -1;
3383 video_port[2] = -1;
3384 }
3385 /* Besides, we may need to SRTP-encrypt this stream */
3386 json_t *s_suite = json_object_get(root, "srtp_suite");
3387 json_t *s_crypto = json_object_get(root, "srtp_crypto");
3388 if(s_suite && s_crypto) {
3389 srtp_suite = json_integer_value(s_suite);
3390 if(srtp_suite != 32 && srtp_suite != 80) {
3391 JANUS_LOG(LOG_ERR, "Invalid SRTP suite (%d)\n", srtp_suite);
3392 error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
3393 g_snprintf(error_cause, 512, "Invalid SRTP suite (%d)", srtp_suite);
3394 goto prepare_response;
3395 }
3396 srtp_crypto = json_string_value(s_crypto);
3397 }
3398 guint64 room_id = json_integer_value(room);
3399 guint64 publisher_id = json_integer_value(pub_id);
3400 const char *host = json_string_value(json_host);
3401 janus_mutex_lock(&rooms_mutex);
3402 janus_videoroom *videoroom = NULL;
3403 error_code = janus_videoroom_access_room(root, TRUE, FALSE, &videoroom, error_cause, sizeof(error_cause));
3404 janus_mutex_unlock(&rooms_mutex);
3405 if(error_code != 0)
3406 goto prepare_response;
3407 janus_refcount_increase(&videoroom->ref);
3408 janus_mutex_lock(&videoroom->mutex);
3409 janus_videoroom_publisher *publisher = g_hash_table_lookup(videoroom->participants, &publisher_id);
3410 if(publisher == NULL) {
3411 janus_mutex_unlock(&videoroom->mutex);
3412 janus_refcount_decrease(&videoroom->ref);
3413 JANUS_LOG(LOG_ERR, "No such publisher (%"SCNu64")\n", publisher_id);
3414 error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_FEED;
3415 g_snprintf(error_cause, 512, "No such feed (%"SCNu64")", publisher_id);
3416 goto prepare_response;
3417 }
3418 janus_refcount_increase(&publisher->ref); /* This is just to handle the request for now */
3419 if(publisher->udp_sock <= 0) {
3420 publisher->udp_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
3421 if(publisher->udp_sock <= 0) {
3422 janus_refcount_decrease(&publisher->ref);
3423 janus_mutex_unlock(&videoroom->mutex);
3424 janus_refcount_decrease(&videoroom->ref);
3425 JANUS_LOG(LOG_ERR, "Could not open UDP socket for rtp stream for publisher (%"SCNu64")\n", publisher_id);
3426 error_code = JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR;
3427 g_snprintf(error_cause, 512, "Could not open UDP socket for rtp stream");
3428 goto prepare_response;
3429 }
3430 }
3431 guint32 audio_handle = 0;
3432 guint32 video_handle[3] = {0, 0, 0};
3433 guint32 data_handle = 0;
3434 if(audio_port > 0) {
3435 audio_handle = janus_videoroom_rtp_forwarder_add_helper(publisher, host, audio_port, audio_rtcp_port, audio_pt, audio_ssrc,
3436 FALSE, srtp_suite, srtp_crypto, 0, FALSE, FALSE);
3437 }
3438 if(video_port[0] > 0) {
3439 video_handle[0] = janus_videoroom_rtp_forwarder_add_helper(publisher, host, video_port[0], video_rtcp_port, video_pt[0], video_ssrc[0],
3440 simulcast, srtp_suite, srtp_crypto, 0, TRUE, FALSE);
3441 }
3442 if(video_port[1] > 0) {
3443 video_handle[1] = janus_videoroom_rtp_forwarder_add_helper(publisher, host, video_port[1], 0, video_pt[1], video_ssrc[1],
3444 FALSE, srtp_suite, srtp_crypto, 1, TRUE, FALSE);
3445 }
3446 if(video_port[2] > 0) {
3447 video_handle[2] = janus_videoroom_rtp_forwarder_add_helper(publisher, host, video_port[2], 0, video_pt[2], video_ssrc[2],
3448 FALSE, srtp_suite, srtp_crypto, 2, TRUE, FALSE);
3449 }
3450 if(data_port > 0) {
3451 data_handle = janus_videoroom_rtp_forwarder_add_helper(publisher, host, data_port, 0, 0, 0, FALSE, 0, NULL, 0, FALSE, TRUE);
3452 }
3453 janus_mutex_unlock(&videoroom->mutex);
3454 response = json_object();
3455 json_t *rtp_stream = json_object();
3456 if(audio_handle > 0) {
3457 json_object_set_new(rtp_stream, "audio_stream_id", json_integer(audio_handle));
3458 json_object_set_new(rtp_stream, "audio", json_integer(audio_port));
3459 /* Also notify event handlers */
3460 if(notify_events && gateway->events_is_enabled()) {
3461 json_t *info = json_object();
3462 json_object_set_new(info, "event", json_string("rtp_forward"));
3463 json_object_set_new(info, "room", json_integer(room_id));
3464 json_object_set_new(info, "publisher_id", json_integer(publisher_id));
3465 json_object_set_new(info, "media", json_string("audio"));
3466 json_object_set_new(info, "stream_id", json_integer(audio_handle));
3467 json_object_set_new(info, "host", json_string(host));
3468 json_object_set_new(info, "port", json_integer(audio_port));
3469 gateway->notify_event(&janus_videoroom_plugin, NULL, info);
3470 }
3471 }
3472 if(video_handle[0] > 0 || video_handle[1] > 0 || video_handle[2] > 0) {
3473 janus_videoroom_reqfir(publisher, "New RTP forward publisher");
3474 /* Done */
3475 if(video_handle[0] > 0) {
3476 json_object_set_new(rtp_stream, "video_stream_id", json_integer(video_handle[0]));
3477 json_object_set_new(rtp_stream, "video", json_integer(video_port[0]));
3478 /* Also notify event handlers */
3479 if(notify_events && gateway->events_is_enabled()) {
3480 json_t *info = json_object();
3481 json_object_set_new(info, "event", json_string("rtp_forward"));
3482 json_object_set_new(info, "room", json_integer(room_id));
3483 json_object_set_new(info, "publisher_id", json_integer(publisher_id));
3484 json_object_set_new(info, "media", json_string("video"));
3485 if(video_handle[1] > 0 || video_handle[2] > 0)
3486 json_object_set_new(info, "video_substream", json_integer(0));
3487 json_object_set_new(info, "stream_id", json_integer(video_handle[0]));
3488 json_object_set_new(info, "host", json_string(host));
3489 json_object_set_new(info, "port", json_integer(video_port[0]));
3490 gateway->notify_event(&janus_videoroom_plugin, NULL, info);
3491 }
3492 }
3493 if(video_handle[1] > 0) {
3494 json_object_set_new(rtp_stream, "video_stream_id_2", json_integer(video_handle[1]));
3495 json_object_set_new(rtp_stream, "video_2", json_integer(video_port[1]));
3496 /* Also notify event handlers */
3497 if(notify_events && gateway->events_is_enabled()) {
3498 json_t *info = json_object();
3499 json_object_set_new(info, "event", json_string("rtp_forward"));
3500 json_object_set_new(info, "room", json_integer(room_id));
3501 json_object_set_new(info, "publisher_id", json_integer(publisher_id));
3502 json_object_set_new(info, "media", json_string("video"));
3503 json_object_set_new(info, "video_substream", json_integer(1));
3504 json_object_set_new(info, "stream_id", json_integer(video_handle[1]));
3505 json_object_set_new(info, "host", json_string(host));
3506 json_object_set_new(info, "port", json_integer(video_port[1]));
3507 gateway->notify_event(&janus_videoroom_plugin, NULL, info);
3508 }
3509 }
3510 if(video_handle[2] > 0) {
3511 json_object_set_new(rtp_stream, "video_stream_id_3", json_integer(video_handle[2]));
3512 json_object_set_new(rtp_stream, "video_3", json_integer(video_port[2]));
3513 /* Also notify event handlers */
3514 if(notify_events && gateway->events_is_enabled()) {
3515 json_t *info = json_object();
3516 json_object_set_new(info, "event", json_string("rtp_forward"));
3517 json_object_set_new(info, "room", json_integer(room_id));
3518 json_object_set_new(info, "publisher_id", json_integer(publisher_id));
3519 json_object_set_new(info, "media", json_string("video"));
3520 json_object_set_new(info, "video_substream", json_integer(2));
3521 json_object_set_new(info, "stream_id", json_integer(video_handle[2]));
3522 json_object_set_new(info, "host", json_string(host));
3523 json_object_set_new(info, "port", json_integer(video_port[2]));
3524 gateway->notify_event(&janus_videoroom_plugin, NULL, info);
3525 }
3526 }
3527 }
3528 if(data_handle > 0) {
3529 json_object_set_new(rtp_stream, "data_stream_id", json_integer(data_handle));
3530 json_object_set_new(rtp_stream, "data", json_integer(data_port));
3531 /* Also notify event handlers */
3532 if(notify_events && gateway->events_is_enabled()) {
3533 json_t *info = json_object();
3534 json_object_set_new(info, "event", json_string("rtp_forward"));
3535 json_object_set_new(info, "room", json_integer(room_id));
3536 json_object_set_new(info, "publisher_id", json_integer(publisher_id));
3537 json_object_set_new(info, "media", json_string("data"));
3538 json_object_set_new(info, "stream_id", json_integer(data_handle));
3539 json_object_set_new(info, "host", json_string(host));
3540 json_object_set_new(info, "port", json_integer(data_port));
3541 gateway->notify_event(&janus_videoroom_plugin, NULL, info);
3542 }
3543 }
3544 /* These two unrefs are related to the message handling */
3545 janus_refcount_decrease(&publisher->ref);
3546 janus_refcount_decrease(&videoroom->ref);
3547 json_object_set_new(rtp_stream, "host", json_string(host));
3548 json_object_set_new(response, "publisher_id", json_integer(publisher_id));
3549 json_object_set_new(response, "rtp_stream", rtp_stream);
3550 json_object_set_new(response, "room", json_integer(room_id));
3551 json_object_set_new(response, "videoroom", json_string("rtp_forward"));
3552 goto prepare_response;
3553 } else if(!strcasecmp(request_text, "stop_rtp_forward")) {
3554 JANUS_VALIDATE_JSON_OBJECT(root, stop_rtp_forward_parameters,
3555 error_code, error_cause, TRUE,
3556 JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT);
3557 if(error_code != 0)
3558 goto prepare_response;
3559 if(lock_rtpfwd && admin_key != NULL) {
3560 /* An admin key was specified: make sure it was provided, and that it's valid */
3561 JANUS_VALIDATE_JSON_OBJECT(root, adminkey_parameters,
3562 error_code, error_cause, TRUE,
3563 JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT);
3564 if(error_code != 0)
3565 goto prepare_response;
3566 JANUS_CHECK_SECRET(admin_key, root, "admin_key", error_code, error_cause,
3567 JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT, JANUS_VIDEOROOM_ERROR_UNAUTHORIZED);
3568 if(error_code != 0)
3569 goto prepare_response;
3570 }
3571 json_t *room = json_object_get(root, "room");
3572 json_t *pub_id = json_object_get(root, "publisher_id");
3573 json_t *id = json_object_get(root, "stream_id");
3574
3575 guint64 room_id = json_integer_value(room);
3576 guint64 publisher_id = json_integer_value(pub_id);
3577 guint32 stream_id = json_integer_value(id);
3578 janus_mutex_lock(&rooms_mutex);
3579 janus_videoroom *videoroom = NULL;
3580 error_code = janus_videoroom_access_room(root, TRUE, FALSE, &videoroom, error_cause, sizeof(error_cause));
3581 janus_mutex_unlock(&rooms_mutex);
3582 if(error_code != 0)
3583 goto prepare_response;
3584 janus_mutex_lock(&videoroom->mutex);
3585 janus_refcount_increase(&videoroom->ref);
3586 janus_videoroom_publisher *publisher = g_hash_table_lookup(videoroom->participants, &publisher_id);
3587 if(publisher == NULL) {
3588 janus_mutex_unlock(&videoroom->mutex);
3589 janus_refcount_decrease(&videoroom->ref);
3590 JANUS_LOG(LOG_ERR, "No such publisher (%"SCNu64")\n", publisher_id);
3591 error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_FEED;
3592 g_snprintf(error_cause, 512, "No such feed (%"SCNu64")", publisher_id);
3593 goto prepare_response;
3594 }
3595 janus_refcount_increase(&publisher->ref); /* Just to handle the message now */
3596 janus_mutex_lock(&publisher->rtp_forwarders_mutex);
3597 if(!g_hash_table_remove(publisher->rtp_forwarders, GUINT_TO_POINTER(stream_id))) {
3598 janus_mutex_unlock(&publisher->rtp_forwarders_mutex);
3599 janus_refcount_decrease(&publisher->ref);
3600 janus_mutex_unlock(&videoroom->mutex);
3601 janus_refcount_decrease(&videoroom->ref);
3602 JANUS_LOG(LOG_ERR, "No such stream (%"SCNu32")\n", stream_id);
3603 error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_FEED;
3604 g_snprintf(error_cause, 512, "No such stream (%"SCNu32")", stream_id);
3605 goto prepare_response;
3606 }
3607 janus_mutex_unlock(&publisher->rtp_forwarders_mutex);
3608 janus_refcount_decrease(&publisher->ref);
3609 janus_mutex_unlock(&videoroom->mutex);
3610 janus_refcount_decrease(&videoroom->ref);
3611 response = json_object();
3612 json_object_set_new(response, "videoroom", json_string("stop_rtp_forward"));
3613 json_object_set_new(response, "room", json_integer(room_id));
3614 json_object_set_new(response, "publisher_id", json_integer(publisher_id));
3615 json_object_set_new(response, "stream_id", json_integer(stream_id));
3616 /* Also notify event handlers */
3617 if(notify_events && gateway->events_is_enabled()) {
3618 json_t *info = json_object();
3619 json_object_set_new(info, "event", json_string("stop_rtp_forward"));
3620 json_object_set_new(info, "room", json_integer(room_id));
3621 json_object_set_new(info, "publisher_id", json_integer(publisher_id));
3622 json_object_set_new(info, "stream_id", json_integer(stream_id));
3623 gateway->notify_event(&janus_videoroom_plugin, NULL, info);
3624 }
3625 goto prepare_response;
3626 } else if(!strcasecmp(request_text, "exists")) {
3627 /* Check whether a given room exists or not, returns true/false */
3628 JANUS_VALIDATE_JSON_OBJECT(root, room_parameters,
3629 error_code, error_cause, TRUE,
3630 JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT);
3631 if(error_code != 0)
3632 goto prepare_response;
3633 json_t *room = json_object_get(root, "room");
3634 guint64 room_id = json_integer_value(room);
3635 janus_mutex_lock(&rooms_mutex);
3636 gboolean room_exists = g_hash_table_contains(rooms, &room_id);
3637 janus_mutex_unlock(&rooms_mutex);
3638 response = json_object();
3639 json_object_set_new(response, "videoroom", json_string("success"));
3640 json_object_set_new(response, "room", json_integer(room_id));
3641 json_object_set_new(response, "exists", room_exists ? json_true() : json_false());
3642 goto prepare_response;
3643 } else if(!strcasecmp(request_text, "allowed")) {
3644 JANUS_LOG(LOG_VERB, "Attempt to edit the list of allowed participants in an existing videoroom room\n");
3645 JANUS_VALIDATE_JSON_OBJECT(root, allowed_parameters,
3646 error_code, error_cause, TRUE,
3647 JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT);
3648 if(error_code != 0)
3649 goto prepare_response;
3650 json_t *action = json_object_get(root, "action");
3651 json_t *room = json_object_get(root, "room");
3652 json_t *allowed = json_object_get(root, "allowed");
3653 const char *action_text = json_string_value(action);
3654 if(strcasecmp(action_text, "enable") && strcasecmp(action_text, "disable") &&
3655 strcasecmp(action_text, "add") && strcasecmp(action_text, "remove")) {
3656 JANUS_LOG(LOG_ERR, "Unsupported action '%s' (allowed)\n", action_text);
3657 error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
3658 g_snprintf(error_cause, 512, "Unsupported action '%s' (allowed)", action_text);
3659 goto prepare_response;
3660 }
3661 guint64 room_id = json_integer_value(room);
3662 janus_mutex_lock(&rooms_mutex);
3663 janus_videoroom *videoroom = NULL;
3664 error_code = janus_videoroom_access_room(root, TRUE, FALSE, &videoroom, error_cause, sizeof(error_cause));
3665 if(error_code != 0) {
3666 janus_mutex_unlock(&rooms_mutex);
3667 goto prepare_response;
3668 }
3669 janus_refcount_increase(&videoroom->ref);
3670 janus_mutex_unlock(&rooms_mutex);
3671 /* A secret may be required for this action */
3672 JANUS_CHECK_SECRET(videoroom->room_secret, root, "secret", error_code, error_cause,
3673 JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT, JANUS_VIDEOROOM_ERROR_UNAUTHORIZED);
3674 if(error_code != 0) {
3675 janus_refcount_decrease(&videoroom->ref);
3676 goto prepare_response;
3677 }
3678 if(!strcasecmp(action_text, "enable")) {
3679 JANUS_LOG(LOG_VERB, "Enabling the check on allowed authorization tokens for room %"SCNu64"\n", room_id);
3680 videoroom->check_allowed = TRUE;
3681 } else if(!strcasecmp(action_text, "disable")) {
3682 JANUS_LOG(LOG_VERB, "Disabling the check on allowed authorization tokens for room %"SCNu64" (free entry)\n", room_id);
3683 videoroom->check_allowed = FALSE;
3684 } else {
3685 gboolean add = !strcasecmp(action_text, "add");
3686 if(allowed) {
3687 /* Make sure the "allowed" array only contains strings */
3688 gboolean ok = TRUE;
3689 if(json_array_size(allowed) > 0) {
3690 size_t i = 0;
3691 for(i=0; i<json_array_size(allowed); i++) {
3692 json_t *a = json_array_get(allowed, i);
3693 if(!a || !json_is_string(a)) {
3694 ok = FALSE;
3695 break;
3696 }
3697 }
3698 }
3699 if(!ok) {
3700 JANUS_LOG(LOG_ERR, "Invalid element in the allowed array (not a string)\n");
3701 error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
3702 g_snprintf(error_cause, 512, "Invalid element in the allowed array (not a string)");
3703 janus_refcount_decrease(&videoroom->ref);
3704 goto prepare_response;
3705 }
3706 size_t i = 0;
3707 for(i=0; i<json_array_size(allowed); i++) {
3708 const char *token = json_string_value(json_array_get(allowed, i));
3709 if(add) {
3710 if(!g_hash_table_lookup(videoroom->allowed, token))
3711 g_hash_table_insert(videoroom->allowed, g_strdup(token), GINT_TO_POINTER(TRUE));
3712 } else {
3713 g_hash_table_remove(videoroom->allowed, token);
3714 }
3715 }
3716 }
3717 }
3718 /* Prepare response */
3719 response = json_object();
3720 json_object_set_new(response, "videoroom", json_string("success"));
3721 json_object_set_new(response, "room", json_integer(videoroom->room_id));
3722 json_t *list = json_array();
3723 if(strcasecmp(action_text, "disable")) {
3724 if(g_hash_table_size(videoroom->allowed) > 0) {
3725 GHashTableIter iter;
3726 gpointer key;
3727 g_hash_table_iter_init(&iter, videoroom->allowed);
3728 while(g_hash_table_iter_next(&iter, &key, NULL)) {
3729 char *token = key;
3730 json_array_append_new(list, json_string(token));
3731 }
3732 }
3733 json_object_set_new(response, "allowed", list);
3734 }
3735 /* Done */
3736 janus_refcount_decrease(&videoroom->ref);
3737 JANUS_LOG(LOG_VERB, "VideoRoom room allowed list updated\n");
3738 goto prepare_response;
3739 } else if(!strcasecmp(request_text, "kick")) {
3740 JANUS_LOG(LOG_VERB, "Attempt to kick a participant from an existing videoroom room\n");
3741 JANUS_VALIDATE_JSON_OBJECT(root, kick_parameters,
3742 error_code, error_cause, TRUE,
3743 JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT);
3744 if(error_code != 0)
3745 goto prepare_response;
3746 json_t *room = json_object_get(root, "room");
3747 json_t *id = json_object_get(root, "id");
3748 guint64 room_id = json_integer_value(room);
3749 janus_mutex_lock(&rooms_mutex);
3750 janus_videoroom *videoroom = NULL;
3751 error_code = janus_videoroom_access_room(root, TRUE, FALSE, &videoroom, error_cause, sizeof(error_cause));
3752 if(error_code != 0) {
3753 janus_mutex_unlock(&rooms_mutex);
3754 goto prepare_response;
3755 }
3756 janus_refcount_increase(&videoroom->ref);
3757 janus_mutex_lock(&videoroom->mutex);
3758 janus_mutex_unlock(&rooms_mutex);
3759 /* A secret may be required for this action */
3760 JANUS_CHECK_SECRET(videoroom->room_secret, root, "secret", error_code, error_cause,
3761 JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT, JANUS_VIDEOROOM_ERROR_UNAUTHORIZED);
3762 if(error_code != 0) {
3763 janus_mutex_unlock(&videoroom->mutex);
3764 janus_refcount_decrease(&videoroom->ref);
3765 goto prepare_response;
3766 }
3767 guint64 user_id = json_integer_value(id);
3768 janus_videoroom_publisher *participant = g_hash_table_lookup(videoroom->participants, &user_id);
3769 if(participant == NULL) {
3770 janus_mutex_unlock(&videoroom->mutex);
3771 janus_refcount_decrease(&videoroom->ref);
3772 JANUS_LOG(LOG_ERR, "No such user %"SCNu64" in room %"SCNu64"\n", user_id, room_id);
3773 error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_FEED;
3774 g_snprintf(error_cause, 512, "No such user %"SCNu64" in room %"SCNu64, user_id, room_id);
3775 goto prepare_response;
3776 }
3777 if(participant->kicked) {
3778 /* Already kicked */
3779 janus_mutex_unlock(&videoroom->mutex);
3780 janus_refcount_decrease(&videoroom->ref);
3781 response = json_object();
3782 json_object_set_new(response, "videoroom", json_string("success"));
3783 /* Done */
3784 goto prepare_response;
3785 }
3786 participant->kicked = TRUE;
3787 participant->session->started = FALSE;
3788 participant->audio_active = FALSE;
3789 participant->video_active = FALSE;
3790 participant->data_active = FALSE;
3791 /* Prepare an event for this */
3792 json_t *kicked = json_object();
3793 json_object_set_new(kicked, "videoroom", json_string("event"));
3794 json_object_set_new(kicked, "room", json_integer(participant->room_id));
3795 json_object_set_new(kicked, "leaving", json_string("ok"));
3796 json_object_set_new(kicked, "reason", json_string("kicked"));
3797 int ret = gateway->push_event(participant->session->handle, &janus_videoroom_plugin, NULL, kicked, NULL);
3798 JANUS_LOG(LOG_VERB, " >> %d (%s)\n", ret, janus_get_api_error(ret));
3799 json_decref(kicked);
3800 janus_mutex_unlock(&videoroom->mutex);
3801 /* If this room requires valid private_id values, we can kick subscriptions too */
3802 if(videoroom->require_pvtid && participant->subscriptions != NULL) {
3803 /* Iterate on the subscriptions we know this user has */
3804 janus_mutex_lock(&participant->subscribers_mutex);
3805 GSList *s = participant->subscriptions;
3806 while(s) {
3807 janus_videoroom_subscriber *subscriber = (janus_videoroom_subscriber *)s->data;
3808 if(subscriber) {
3809 subscriber->kicked = TRUE;
3810 subscriber->audio = FALSE;
3811 subscriber->video = FALSE;
3812 subscriber->data = FALSE;
3813 /* FIXME We should also close the PeerConnection, but we risk race conditions if we do it here,
3814 * so for now we mark the subscriber as kicked and prevent it from getting any media after this */
3815 }
3816 s = s->next;
3817 }
3818 janus_mutex_unlock(&participant->subscribers_mutex);
3819 }
3820 /* This publisher is leaving, tell everybody */
3821 janus_videoroom_leave_or_unpublish(participant, TRUE, TRUE);
3822 /* Tell the core to tear down the PeerConnection, hangup_media will do the rest */
3823 if(participant && participant->session)
3824 gateway->close_pc(participant->session->handle);
3825 JANUS_LOG(LOG_INFO, "Kicked user %"SCNu64" from room %"SCNu64"\n", user_id, room_id);
3826 /* Prepare response */
3827 response = json_object();
3828 json_object_set_new(response, "videoroom", json_string("success"));
3829 /* Done */
3830 janus_refcount_decrease(&videoroom->ref);
3831 goto prepare_response;
3832 } else if(!strcasecmp(request_text, "listparticipants")) {
3833 /* List all participants in a room, specifying whether they're publishers or just attendees */
3834 JANUS_VALIDATE_JSON_OBJECT(root, room_parameters,
3835 error_code, error_cause, TRUE,
3836 JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT);
3837 if(error_code != 0)
3838 goto prepare_response;
3839 json_t *room = json_object_get(root, "room");
3840 guint64 room_id = json_integer_value(room);
3841 janus_mutex_lock(&rooms_mutex);
3842 janus_videoroom *videoroom = NULL;
3843 error_code = janus_videoroom_access_room(root, FALSE, FALSE, &videoroom, error_cause, sizeof(error_cause));
3844 janus_mutex_unlock(&rooms_mutex);
3845 if(error_code != 0)
3846 goto prepare_response;
3847 janus_refcount_increase(&videoroom->ref);
3848 /* Return a list of all participants (whether they're publishing or not) */
3849 json_t *list = json_array();
3850 GHashTableIter iter;
3851 gpointer value;
3852 janus_mutex_lock(&videoroom->mutex);
3853 g_hash_table_iter_init(&iter, videoroom->participants);
3854 while (!g_atomic_int_get(&videoroom->destroyed) && g_hash_table_iter_next(&iter, NULL, &value)) {
3855 janus_videoroom_publisher *p = value;
3856 json_t *pl = json_object();
3857 json_object_set_new(pl, "id", json_integer(p->user_id));
3858 if(p->display)
3859 json_object_set_new(pl, "display", json_string(p->display));
3860 json_object_set_new(pl, "publisher", (p->sdp && p->session->started) ? json_true() : json_false());
3861 if((p->sdp && p->session->started)) {
3862 if(p->audio_level_extmap_id > 0)
3863 json_object_set_new(pl, "talking", p->talking ? json_true() : json_false());
3864 }
3865 json_array_append_new(list, pl);
3866 }
3867 janus_mutex_unlock(&videoroom->mutex);
3868 janus_refcount_decrease(&videoroom->ref);
3869 response = json_object();
3870 json_object_set_new(response, "videoroom", json_string("participants"));
3871 json_object_set_new(response, "room", json_integer(room_id));
3872 json_object_set_new(response, "participants", list);
3873 goto prepare_response;
3874 } else if(!strcasecmp(request_text, "listforwarders")) {
3875 /* List all forwarders in a room */
3876 JANUS_VALIDATE_JSON_OBJECT(root, room_parameters,
3877 error_code, error_cause, TRUE,
3878 JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT);
3879 if(error_code != 0)
3880 goto prepare_response;
3881 json_t *room = json_object_get(root, "room");
3882 guint64 room_id = json_integer_value(room);
3883 janus_mutex_lock(&rooms_mutex);
3884 janus_videoroom *videoroom = g_hash_table_lookup(rooms, &room_id);
3885 if(videoroom == NULL) {
3886 JANUS_LOG(LOG_ERR, "No such room (%"SCNu64")\n", room_id);
3887 error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM;
3888 g_snprintf(error_cause, 512, "No such room (%"SCNu64")", room_id);
3889 janus_mutex_unlock(&rooms_mutex);
3890 goto prepare_response;
3891 }
3892 if(g_atomic_int_get(&videoroom->destroyed)) {
3893 JANUS_LOG(LOG_ERR, "No such room (%"SCNu64")\n", room_id);
3894 error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM;
3895 g_snprintf(error_cause, 512, "No such room (%"SCNu64")", room_id);
3896 janus_mutex_unlock(&rooms_mutex);
3897 goto prepare_response;
3898 }
3899 /* A secret may be required for this action */
3900 JANUS_CHECK_SECRET(videoroom->room_secret, root, "secret", error_code, error_cause,
3901 JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT, JANUS_VIDEOROOM_ERROR_UNAUTHORIZED);
3902 if(error_code != 0) {
3903 janus_mutex_unlock(&rooms_mutex);
3904 goto prepare_response;
3905 }
3906 /* Return a list of all forwarders */
3907 json_t *list = json_array();
3908 GHashTableIter iter;
3909 gpointer value;
3910 janus_mutex_lock(&videoroom->mutex);
3911 g_hash_table_iter_init(&iter, videoroom->participants);
3912 while (!g_atomic_int_get(&videoroom->destroyed) && g_hash_table_iter_next(&iter, NULL, &value)) {
3913 janus_videoroom_publisher *p = value;
3914 janus_mutex_lock(&p->rtp_forwarders_mutex);
3915 if(g_hash_table_size(p->rtp_forwarders) == 0) {
3916 janus_mutex_unlock(&p->rtp_forwarders_mutex);
3917 continue;
3918 }
3919 json_t *pl = json_object();
3920 json_object_set_new(pl, "publisher_id", json_integer(p->user_id));
3921 if(p->display)
3922 json_object_set_new(pl, "display", json_string(p->display));
3923 json_t *flist = json_array();
3924 GHashTableIter iter_f;
3925 gpointer key_f, value_f;
3926 g_hash_table_iter_init(&iter_f, p->rtp_forwarders);
3927 while(g_hash_table_iter_next(&iter_f, &key_f, &value_f)) {
3928 json_t *fl = json_object();
3929 guint32 rpk = GPOINTER_TO_UINT(key_f);
3930 janus_videoroom_rtp_forwarder *rpv = value_f;
3931 json_object_set_new(fl, "ip", json_string(inet_ntoa(rpv->serv_addr.sin_addr)));
3932 if(rpv->is_data) {
3933 json_object_set_new(fl, "data_stream_id", json_integer(rpk));
3934 json_object_set_new(fl, "port", json_integer(ntohs(rpv->serv_addr.sin_port)));
3935 } else if(rpv->is_video) {
3936 json_object_set_new(fl, "video_stream_id", json_integer(rpk));
3937 json_object_set_new(fl, "port", json_integer(ntohs(rpv->serv_addr.sin_port)));
3938 if(rpv->local_rtcp_port > 0)
3939 json_object_set_new(fl, "local_rtcp_port", json_integer(rpv->local_rtcp_port));
3940 if(rpv->remote_rtcp_port > 0)
3941 json_object_set_new(fl, "remote_rtcp_port", json_integer(rpv->remote_rtcp_port));
3942 if(rpv->payload_type)
3943 json_object_set_new(fl, "pt", json_integer(rpv->payload_type));
3944 if(rpv->ssrc)
3945 json_object_set_new(fl, "ssrc", json_integer(rpv->ssrc));
3946 if(rpv->substream)
3947 json_object_set_new(fl, "substream", json_integer(rpv->substream));
3948 } else {
3949 json_object_set_new(fl, "audio_stream_id", json_integer(rpk));
3950 json_object_set_new(fl, "port", json_integer(ntohs(rpv->serv_addr.sin_port)));
3951 if(rpv->local_rtcp_port > 0)
3952 json_object_set_new(fl, "local_rtcp_port", json_integer(rpv->local_rtcp_port));
3953 if(rpv->remote_rtcp_port > 0)
3954 json_object_set_new(fl, "remote_rtcp_port", json_integer(rpv->remote_rtcp_port));
3955 if(rpv->payload_type)
3956 json_object_set_new(fl, "pt", json_integer(rpv->payload_type));
3957 if(rpv->ssrc)
3958 json_object_set_new(fl, "ssrc", json_integer(rpv->ssrc));
3959 }
3960 if(rpv->is_srtp)
3961 json_object_set_new(fl, "srtp", json_true());
3962 json_array_append_new(flist, fl);
3963 }
3964 janus_mutex_unlock(&p->rtp_forwarders_mutex);
3965 json_object_set_new(pl, "rtp_forwarder", flist);
3966 json_array_append_new(list, pl);
3967 }
3968 janus_mutex_unlock(&videoroom->mutex);
3969 janus_mutex_unlock(&rooms_mutex);
3970 response = json_object();
3971 json_object_set_new(response, "videoroom", json_string("forwarders"));
3972 json_object_set_new(response, "room", json_integer(room_id));
3973 json_object_set_new(response, "rtp_forwarders", list);
3974 goto prepare_response;
3975 } else {
3976 /* Not a request we recognize, don't do anything */
3977 return NULL;
3978 }
3979
3980prepare_response:
3981 {
3982 if(error_code == 0 && !response) {
3983 error_code = JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR;
3984 g_snprintf(error_cause, 512, "Invalid response");
3985 }
3986 if(error_code != 0) {
3987 /* Prepare JSON error event */
3988 response = json_object();
3989 json_object_set_new(response, "videoroom", json_string("event"));
3990 json_object_set_new(response, "error_code", json_integer(error_code));
3991 json_object_set_new(response, "error", json_string(error_cause));
3992 }
3993 return response;
3994 }
3995
3996}
3997
3998struct janus_plugin_result *janus_videoroom_handle_message(janus_plugin_session *handle, char *transaction, json_t *message, json_t *jsep) {
3999 if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
4000 return janus_plugin_result_new(JANUS_PLUGIN_ERROR, g_atomic_int_get(&stopping) ? "Shutting down" : "Plugin not initialized", NULL);
4001
4002 /* Pre-parse the message */
4003 int error_code = 0;
4004 char error_cause[512];
4005 json_t *root = message;
4006 json_t *response = NULL;
4007
4008 janus_mutex_lock(&sessions_mutex);
4009 janus_videoroom_session *session = janus_videoroom_lookup_session(handle);
4010 if(!session) {
4011 janus_mutex_unlock(&sessions_mutex);
4012 JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
4013 error_code = JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR;
4014 g_snprintf(error_cause, 512, "%s", "No session associated with this handle...");
4015 goto plugin_response;
4016 }
4017 /* Increase the reference counter for this session: we'll decrease it after we handle the message */
4018 janus_refcount_increase(&session->ref);
4019 janus_mutex_unlock(&sessions_mutex);
4020 if(g_atomic_int_get(&session->destroyed)) {
4021 JANUS_LOG(LOG_ERR, "Session has already been marked as destroyed...\n");
4022 error_code = JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR;
4023 g_snprintf(error_cause, 512, "%s", "Session has already been marked as destroyed...");
4024 goto plugin_response;
4025 }
4026
4027 if(message == NULL) {
4028 JANUS_LOG(LOG_ERR, "No message??\n");
4029 error_code = JANUS_VIDEOROOM_ERROR_NO_MESSAGE;
4030 g_snprintf(error_cause, 512, "%s", "No message??");
4031 goto plugin_response;
4032 }
4033 if(!json_is_object(root)) {
4034 JANUS_LOG(LOG_ERR, "JSON error: not an object\n");
4035 error_code = JANUS_VIDEOROOM_ERROR_INVALID_JSON;
4036 g_snprintf(error_cause, 512, "JSON error: not an object");
4037 goto plugin_response;
4038 }
4039 /* Get the request first */
4040 JANUS_VALIDATE_JSON_OBJECT(root, request_parameters,
4041 error_code, error_cause, TRUE,
4042 JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT);
4043 if(error_code != 0)
4044 goto plugin_response;
4045 json_t *request = json_object_get(root, "request");
4046 /* Some requests ('create', 'destroy', 'exists', 'list') can be handled synchronously */
4047 const char *request_text = json_string_value(request);
4048 /* We have a separate method to process synchronous requests, as those may
4049 * arrive from the Admin API as well, and so we handle them the same way */
4050 response = janus_videoroom_process_synchronous_request(session, root);
4051 if(response != NULL) {
4052 /* We got a response, send it back */
4053 goto plugin_response;
4054 } else if(!strcasecmp(request_text, "join") || !strcasecmp(request_text, "joinandconfigure")
4055 || !strcasecmp(request_text, "configure") || !strcasecmp(request_text, "publish") || !strcasecmp(request_text, "unpublish")
4056 || !strcasecmp(request_text, "start") || !strcasecmp(request_text, "pause") || !strcasecmp(request_text, "switch")
4057 || !strcasecmp(request_text, "leave")) {
4058 /* These messages are handled asynchronously */
4059
4060 janus_videoroom_message *msg = g_malloc(sizeof(janus_videoroom_message));
4061 msg->handle = handle;
4062 msg->transaction = transaction;
4063 msg->message = root;
4064 msg->jsep = jsep;
4065 g_async_queue_push(messages, msg);
4066
4067 return janus_plugin_result_new(JANUS_PLUGIN_OK_WAIT, NULL, NULL);
4068 } else {
4069 JANUS_LOG(LOG_VERB, "Unknown request '%s'\n", request_text);
4070 error_code = JANUS_VIDEOROOM_ERROR_INVALID_REQUEST;
4071 g_snprintf(error_cause, 512, "Unknown request '%s'", request_text);
4072 }
4073
4074plugin_response:
4075 {
4076 if(error_code == 0 && !response) {
4077 error_code = JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR;
4078 g_snprintf(error_cause, 512, "Invalid response");
4079 }
4080 if(error_code != 0) {
4081 /* Prepare JSON error event */
4082 json_t *event = json_object();
4083 json_object_set_new(event, "videoroom", json_string("event"));
4084 json_object_set_new(event, "error_code", json_integer(error_code));
4085 json_object_set_new(event, "error", json_string(error_cause));
4086 response = event;
4087 }
4088 if(root != NULL)
4089 json_decref(root);
4090 if(jsep != NULL)
4091 json_decref(jsep);
4092 g_free(transaction);
4093
4094 if(session != NULL)
4095 janus_refcount_decrease(&session->ref);
4096 return janus_plugin_result_new(JANUS_PLUGIN_OK, NULL, response);
4097 }
4098
4099}
4100
4101json_t *janus_videoroom_handle_admin_message(json_t *message) {
4102 /* Some requests (e.g., 'create' and 'destroy') can be handled via Admin API */
4103 int error_code = 0;
4104 char error_cause[512];
4105 json_t *response = NULL;
4106
4107 JANUS_VALIDATE_JSON_OBJECT(message, request_parameters,
4108 error_code, error_cause, TRUE,
4109 JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT);
4110 if(error_code != 0)
4111 goto admin_response;
4112 json_t *request = json_object_get(message, "request");
4113 const char *request_text = json_string_value(request);
4114 if((response = janus_videoroom_process_synchronous_request(NULL, message)) != NULL) {
4115 /* We got a response, send it back */
4116 goto admin_response;
4117 } else {
4118 JANUS_LOG(LOG_VERB, "Unknown request '%s'\n", request_text);
4119 error_code = JANUS_VIDEOROOM_ERROR_INVALID_REQUEST;
4120 g_snprintf(error_cause, 512, "Unknown request '%s'", request_text);
4121 }
4122
4123admin_response:
4124 {
4125 if(!response) {
4126 /* Prepare JSON error event */
4127 response = json_object();
4128 json_object_set_new(response, "streaming", json_string("event"));
4129 json_object_set_new(response, "error_code", json_integer(error_code));
4130 json_object_set_new(response, "error", json_string(error_cause));
4131 }
4132 return response;
4133 }
4134
4135}
4136
4137void janus_videoroom_setup_media(janus_plugin_session *handle) {
4138 JANUS_LOG(LOG_INFO, "[%s-%p] WebRTC media is now available\n", JANUS_VIDEOROOM_PACKAGE, handle);
4139 if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
4140 return;
4141 janus_mutex_lock(&sessions_mutex);
4142 janus_videoroom_session *session = janus_videoroom_lookup_session(handle);
4143 if(!session) {
4144 janus_mutex_unlock(&sessions_mutex);
4145 JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
4146 return;
4147 }
4148 if(g_atomic_int_get(&session->destroyed)) {
4149 janus_mutex_unlock(&sessions_mutex);
4150 return;
4151 }
4152 g_atomic_int_set(&session->hangingup, 0);
4153
4154 /* Media relaying can start now */
4155 session->started = TRUE;
4156 if(session->participant) {
4157 /* If this is a publisher, notify all subscribers about the fact they can
4158 * now subscribe; if this is a subscriber, instead, ask the publisher a FIR */
4159 if(session->participant_type == janus_videoroom_p_type_publisher) {
4160 janus_videoroom_publisher *participant = janus_videoroom_session_get_publisher(session);
4161 /* Notify all other participants that there's a new boy in town */
4162 json_t *list = json_array();
4163 json_t *pl = json_object();
4164 json_object_set_new(pl, "id", json_integer(participant->user_id));
4165 if(participant->display)
4166 json_object_set_new(pl, "display", json_string(participant->display));
4167 if(participant->audio)
4168 json_object_set_new(pl, "audio_codec", json_string(janus_audiocodec_name(participant->acodec)));
4169 if(participant->video)
4170 json_object_set_new(pl, "video_codec", json_string(janus_videocodec_name(participant->vcodec)));
4171 if(participant->ssrc[0] || participant->rid[0])
4172 json_object_set_new(pl, "simulcast", json_true());
4173 if(participant->audio_level_extmap_id > 0)
4174 json_object_set_new(pl, "talking", participant->talking ? json_true() : json_false());
4175 json_array_append_new(list, pl);
4176 json_t *pub = json_object();
4177 json_object_set_new(pub, "videoroom", json_string("event"));
4178 json_object_set_new(pub, "room", json_integer(participant->room_id));
4179 json_object_set_new(pub, "publishers", list);
4180 if (participant->room) {
4181 janus_mutex_lock(&participant->room->mutex);
4182 janus_videoroom_notify_participants(participant, pub);
4183 janus_mutex_unlock(&participant->room->mutex);
4184 }
4185 json_decref(pub);
4186 /* Also notify event handlers */
4187 if(notify_events && gateway->events_is_enabled()) {
4188 json_t *info = json_object();
4189 json_object_set_new(info, "event", json_string("published"));
4190 json_object_set_new(info, "room", json_integer(participant->room_id));
4191 json_object_set_new(info, "id", json_integer(participant->user_id));
4192 gateway->notify_event(&janus_videoroom_plugin, session->handle, info);
4193 }
4194 janus_refcount_decrease(&participant->ref);
4195 } else if(session->participant_type == janus_videoroom_p_type_subscriber) {
4196 janus_videoroom_subscriber *s = (janus_videoroom_subscriber *)session->participant;
4197 if(s && s->feed) {
4198 janus_videoroom_publisher *p = s->feed;
4199 if(p && p->session) {
4200 janus_videoroom_reqfir(p, "New subscriber available");
4201 /* Also notify event handlers */
4202 if(notify_events && gateway->events_is_enabled()) {
4203 json_t *info = json_object();
4204 json_object_set_new(info, "event", json_string("subscribed"));
4205 json_object_set_new(info, "room", json_integer(p->room_id));
4206 json_object_set_new(info, "feed", json_integer(p->user_id));
4207 gateway->notify_event(&janus_videoroom_plugin, session->handle, info);
4208 }
4209 }
4210 }
4211 }
4212 }
4213 janus_mutex_unlock(&sessions_mutex);
4214}
4215
4216void janus_videoroom_incoming_rtp(janus_plugin_session *handle, int video, char *buf, int len) {
4217 if(handle == NULL || g_atomic_int_get(&handle->stopped) || g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized) || !gateway)
4218 return;
4219 janus_videoroom_session *session = (janus_videoroom_session *)handle->plugin_handle;
4220 if(!session || g_atomic_int_get(&session->destroyed) || session->participant_type != janus_videoroom_p_type_publisher)
4221 return;
4222 janus_videoroom_publisher *participant = janus_videoroom_session_get_publisher_nodebug(session);
4223 if(participant == NULL)
4224 return;
4225 if(g_atomic_int_get(&participant->destroyed) || participant->kicked || participant->room == NULL) {
4226 janus_videoroom_publisher_dereference_nodebug(participant);
4227 return;
4228 }
4229 janus_videoroom *videoroom = participant->room;
4230
4231 /* In case this is an audio packet and we're doing talk detection, check the audio level extension */
4232 if(!video && videoroom->audiolevel_event && participant->audio_active) {
4233 int level = 0;
4234 if(janus_rtp_header_extension_parse_audio_level(buf, len, participant->audio_level_extmap_id, &level) == 0) {
4235 participant->audio_dBov_sum += level;
4236 participant->audio_active_packets++;
4237 participant->audio_dBov_level = level;
4238 if(participant->audio_active_packets > 0 && participant->audio_active_packets == videoroom->audio_active_packets) {
4239 gboolean notify_talk_event = FALSE;
4240 float audio_dBov_avg = (float)participant->audio_dBov_sum/(float)participant->audio_active_packets;
4241 if(audio_dBov_avg < videoroom->audio_level_average) {
4242 /* Participant talking, should we notify all participants? */
4243 if(!participant->talking)
4244 notify_talk_event = TRUE;
4245 participant->talking = TRUE;
4246 } else {
4247 /* Participant not talking anymore, should we notify all participants? */
4248 if(participant->talking)
4249 notify_talk_event = TRUE;
4250 participant->talking = FALSE;
4251 }
4252 participant->audio_active_packets = 0;
4253 participant->audio_dBov_sum = 0;
4254 /* Only notify in case of state changes */
4255 if(notify_talk_event) {
4256 janus_mutex_lock(&videoroom->mutex);
4257 json_t *event = json_object();
4258 json_object_set_new(event, "videoroom", json_string(participant->talking ? "talking" : "stopped-talking"));
4259 json_object_set_new(event, "room", json_integer(videoroom->room_id));
4260 json_object_set_new(event, "id", json_integer(participant->user_id));
4261 json_object_set_new(event, "audio-level-dBov-avg", json_real(audio_dBov_avg));
4262 janus_videoroom_notify_participants(participant, event);
4263 json_decref(event);
4264 janus_mutex_unlock(&videoroom->mutex);
4265 /* Also notify event handlers */
4266 if(notify_events && gateway->events_is_enabled()) {
4267 json_t *info = json_object();
4268 json_object_set_new(info, "videoroom", json_string(participant->talking ? "talking" : "stopped-talking"));
4269 json_object_set_new(info, "room", json_integer(videoroom->room_id));
4270 json_object_set_new(info, "id", json_integer(participant->user_id));
4271 json_object_set_new(event, "audio-level-dBov-avg", json_real(audio_dBov_avg));
4272 gateway->notify_event(&janus_videoroom_plugin, session->handle, info);
4273 }
4274 }
4275 }
4276 }
4277 }
4278
4279 if((!video && participant->audio_active) || (video && participant->video_active)) {
4280 janus_rtp_header *rtp = (janus_rtp_header *)buf;
4281 int sc = video ? 0 : -1;
4282 /* Check if we're simulcasting, and if so, keep track of the "layer" */
4283 if(video && (participant->ssrc[0] != 0 || participant->rid[0] != NULL)) {
4284 uint32_t ssrc = ntohl(rtp->ssrc);
4285 if(ssrc == participant->ssrc[0])
4286 sc = 0;
4287 else if(ssrc == participant->ssrc[1])
4288 sc = 1;
4289 else if(ssrc == participant->ssrc[2])
4290 sc = 2;
4291 else if(participant->rid_extmap_id > 0) {
4292 /* We may not know the SSRC yet, try the rid RTP extension */
4293 char sdes_item[16];
4294 if(janus_rtp_header_extension_parse_rid(buf, len, participant->rid_extmap_id, sdes_item, sizeof(sdes_item)) == 0) {
4295 if(participant->rid[2] != NULL && !strcmp(participant->rid[2], sdes_item)) {
4296 participant->ssrc[0] = ssrc;
4297 sc = 0;
4298 } else if(participant->rid[1] != NULL && !strcmp(participant->rid[1], sdes_item)) {
4299 participant->ssrc[1] = ssrc;
4300 sc = 1;
4301 } else if(participant->rid[0] != NULL && !strcmp(participant->rid[0], sdes_item)) {
4302 participant->ssrc[2] = ssrc;
4303 sc = 2;
4304 }
4305 }
4306 }
4307 }
4308 /* Forward RTP to the appropriate port for the rtp_forwarders associated with this publisher, if there are any */
4309 janus_mutex_lock(&participant->rtp_forwarders_mutex);
4310 if(participant->srtp_contexts && g_hash_table_size(participant->srtp_contexts) > 0) {
4311 GHashTableIter iter;
4312 gpointer value;
4313 g_hash_table_iter_init(&iter, participant->srtp_contexts);
4314 while(g_hash_table_iter_next(&iter, NULL, &value)) {
4315 janus_videoroom_srtp_context *srtp_ctx = (janus_videoroom_srtp_context *)value;
4316 srtp_ctx->slen = 0;
4317 }
4318 }
4319 GHashTableIter iter;
4320 gpointer value;
4321 g_hash_table_iter_init(&iter, participant->rtp_forwarders);
4322 while(participant->udp_sock > 0 && g_hash_table_iter_next(&iter, NULL, &value)) {
4323 janus_videoroom_rtp_forwarder *rtp_forward = (janus_videoroom_rtp_forwarder *)value;
4324 if(rtp_forward->is_data || (video && !rtp_forward->is_video) || (!video && rtp_forward->is_video))
4325 continue;
4326 /* Backup the RTP header info, as we may rewrite part of it */
4327 uint32_t seq_number = ntohs(rtp->seq_number);
4328 uint32_t timestamp = ntohl(rtp->timestamp);
4329 int pt = rtp->type;
4330 uint32_t ssrc = ntohl(rtp->ssrc);
4331 /* First of all, check if we're simulcasting and if we need to forward or ignore this frame */
4332 if(video && !rtp_forward->simulcast && rtp_forward->substream != sc) {
4333 continue;
4334 } else if(video && rtp_forward->simulcast) {
4335 /* This is video and we're simulcasting, check if we need to forward this frame */
4336 if(!janus_rtp_simulcasting_context_process_rtp(&rtp_forward->sim_context,
4337 buf, len, participant->ssrc, participant->rid, participant->vcodec, &rtp_forward->context))
4338 continue;
4339 janus_rtp_header_update(rtp, &rtp_forward->context, TRUE, 4500);
4340 /* By default we use a fixed SSRC (it may be overwritten later) */
4341 rtp->ssrc = htonl(participant->user_id & 0xffffffff);
4342 }
4343 /* Check if payload type and/or SSRC need to be overwritten for this forwarder */
4344 if(rtp_forward->payload_type > 0)
4345 rtp->type = rtp_forward->payload_type;
4346 if(rtp_forward->ssrc > 0)
4347 rtp->ssrc = htonl(rtp_forward->ssrc);
4348 /* Check if this is an RTP or SRTP forwarder */
4349 if(!rtp_forward->is_srtp) {
4350 /* Plain RTP */
4351 if(sendto(participant->udp_sock, buf, len, 0, (struct sockaddr*)&rtp_forward->serv_addr, sizeof(rtp_forward->serv_addr)) < 0) {
4352 JANUS_LOG(LOG_HUGE, "Error forwarding RTP %s packet for %s... %s (len=%d)...\n",
4353 (video ? "video" : "audio"), participant->display, strerror(errno), len);
4354 }
4355 } else {
4356 /* SRTP: check if we already encrypted the packet before */
4357 if(rtp_forward->srtp_ctx->slen == 0) {
4358 memcpy(&rtp_forward->srtp_ctx->sbuf, buf, len);
4359 int protected = len;
4360 int res = srtp_protect(rtp_forward->srtp_ctx->ctx, &rtp_forward->srtp_ctx->sbuf, &protected);
4361 if(res != srtp_err_status_ok) {
4362 janus_rtp_header *header = (janus_rtp_header *)&rtp_forward->srtp_ctx->sbuf;
4363 guint32 timestamp = ntohl(header->timestamp);
4364 guint16 seq = ntohs(header->seq_number);
4365 JANUS_LOG(LOG_ERR, "Error encrypting %s packet for %s... %s (len=%d-->%d, ts=%"SCNu32", seq=%"SCNu16")...\n",
4366 (video ? "Video" : "Audio"), participant->display, janus_srtp_error_str(res), len, protected, timestamp, seq);
4367 } else {
4368 rtp_forward->srtp_ctx->slen = protected;
4369 }
4370 }
4371 if(rtp_forward->srtp_ctx->slen > 0 && sendto(participant->udp_sock, rtp_forward->srtp_ctx->sbuf, rtp_forward->srtp_ctx->slen, 0, (struct sockaddr*)&rtp_forward->serv_addr, sizeof(rtp_forward->serv_addr)) < 0) {
4372 JANUS_LOG(LOG_HUGE, "Error forwarding SRTP %s packet for %s... %s (len=%d)...\n",
4373 (video ? "video" : "audio"), participant->display, strerror(errno), rtp_forward->srtp_ctx->slen);
4374 }
4375 }
4376 /* Restore original values of payload type and SSRC before going on */
4377 rtp->type = pt;
4378 rtp->ssrc = htonl(ssrc);
4379 rtp->timestamp = htonl(timestamp);
4380 rtp->seq_number = htons(seq_number);
4381 }
4382 janus_mutex_unlock(&participant->rtp_forwarders_mutex);
4383 /* Set the payload type of the publisher */
4384 rtp->type = video ? participant->video_pt : participant->audio_pt;
4385 /* Save the frame if we're recording */
4386 if(!video || (participant->ssrc[0] == 0 && participant->rid[0] == NULL)) {
4387 janus_recorder_save_frame(video ? participant->vrc : participant->arc, buf, len);
4388 } else {
4389 /* We're simulcasting, save the best video quality */
4390 gboolean save = janus_rtp_simulcasting_context_process_rtp(&participant->rec_simctx,
4391 buf, len, participant->ssrc, participant->rid, participant->vcodec, &participant->rec_ctx);
4392 if(save) {
4393 uint32_t seq_number = ntohs(rtp->seq_number);
4394 uint32_t timestamp = ntohl(rtp->timestamp);
4395 uint32_t ssrc = ntohl(rtp->ssrc);
4396 janus_rtp_header_update(rtp, &participant->rec_ctx, TRUE, 4500);
4397 /* We use a fixed SSRC for the whole recording */
4398 rtp->ssrc = htonl(participant->user_id & 0xffffffff);
4399 janus_recorder_save_frame(participant->vrc, buf, len);
4400 /* Restore the header, as it will be needed by subscribers */
4401 rtp->ssrc = htonl(ssrc);
4402 rtp->timestamp = htonl(timestamp);
4403 rtp->seq_number = htons(seq_number);
4404 }
4405 }
4406 /* Done, relay it */
4407 janus_videoroom_rtp_relay_packet packet;
4408 packet.data = rtp;
4409 packet.length = len;
4410 packet.is_video = video;
4411 packet.svc = FALSE;
4412 if(video && videoroom->do_svc) {
4413 /* We're doing SVC: let's parse this packet to see which layers are there */
4414 int plen = 0;
4415 char *payload = janus_rtp_payload(buf, len, &plen);
4416 if(payload == NULL)
4417 return;
4418 uint8_t pbit = 0, dbit = 0, ubit = 0, bbit = 0, ebit = 0;
4419 int found = 0, spatial_layer = 0, temporal_layer = 0;
4420 if(janus_vp9_parse_svc(payload, plen, &found, &spatial_layer, &temporal_layer, &pbit, &dbit, &ubit, &bbit, &ebit) == 0) {
4421 if(found) {
4422 packet.svc = TRUE;
4423 packet.spatial_layer = spatial_layer;
4424 packet.temporal_layer = temporal_layer;
4425 packet.pbit = pbit;
4426 packet.dbit = dbit;
4427 packet.ubit = ubit;
4428 packet.bbit = bbit;
4429 packet.ebit = ebit;
4430 }
4431 }
4432 }
4433 packet.ssrc[0] = (sc != -1 ? participant->ssrc[0] : 0);
4434 packet.ssrc[1] = (sc != -1 ? participant->ssrc[1] : 0);
4435 packet.ssrc[2] = (sc != -1 ? participant->ssrc[2] : 0);
4436 /* Backup the actual timestamp and sequence number set by the publisher, in case switching is involved */
4437 packet.timestamp = ntohl(packet.data->timestamp);
4438 packet.seq_number = ntohs(packet.data->seq_number);
4439 /* Go: some viewers may decide to drop the packet, but that's up to them */
4440 janus_mutex_lock_nodebug(&participant->subscribers_mutex);
4441 g_slist_foreach(participant->subscribers, janus_videoroom_relay_rtp_packet, &packet);
4442 janus_mutex_unlock_nodebug(&participant->subscribers_mutex);
4443
4444 /* Check if we need to send any REMB, FIR or PLI back to this publisher */
4445 if(video && participant->video_active) {
4446 /* Did we send a REMB already, or is it time to send one? */
4447 gboolean send_remb = FALSE;
4448 if(participant->remb_latest == 0 && participant->remb_startup > 0) {
4449 /* Still in the starting phase, send the ramp-up REMB feedback */
4450 send_remb = TRUE;
4451 } else if(participant->remb_latest > 0 && janus_get_monotonic_time()-participant->remb_latest >= 5*G_USEC_PER_SEC) {
4452 /* 5 seconds have passed since the last REMB, send a new one */
4453 send_remb = TRUE;
4454 }
4455
4456 if(send_remb && participant->bitrate) {
4457 /* We send a few incremental REMB messages at startup */
4458 uint32_t bitrate = participant->bitrate;
4459 if(participant->remb_startup > 0) {
4460 bitrate = bitrate/participant->remb_startup;
4461 participant->remb_startup--;
4462 }
4463 JANUS_LOG(LOG_VERB, "Sending REMB (%s, %"SCNu32")\n", participant->display, bitrate);
4464 char rtcpbuf[24];
4465 janus_rtcp_remb((char *)(&rtcpbuf), 24, bitrate);
4466 gateway->relay_rtcp(handle, video, rtcpbuf, 24);
4467 if(participant->remb_startup == 0)
4468 participant->remb_latest = janus_get_monotonic_time();
4469 }
4470 /* Generate FIR/PLI too, if needed */
4471 if(video && participant->video_active && (videoroom->fir_freq > 0)) {
4472 /* We generate RTCP every tot seconds/frames */
4473 gint64 now = janus_get_monotonic_time();
4474 /* First check if this is a keyframe, though: if so, we reset the timer */
4475 int plen = 0;
4476 char *payload = janus_rtp_payload(buf, len, &plen);
4477 if(payload == NULL)
4478 return;
4479 if(participant->vcodec == JANUS_VIDEOCODEC_VP8) {
4480 if(janus_vp8_is_keyframe(payload, plen))
4481 participant->fir_latest = now;
4482 } else if(participant->vcodec == JANUS_VIDEOCODEC_VP9) {
4483 if(janus_vp9_is_keyframe(payload, plen))
4484 participant->fir_latest = now;
4485 } else if(participant->vcodec == JANUS_VIDEOCODEC_H264) {
4486 if(janus_h264_is_keyframe(payload, plen))
4487 participant->fir_latest = now;
4488 }
4489 if((now-participant->fir_latest) >= ((gint64)videoroom->fir_freq*G_USEC_PER_SEC)) {
4490 /* FIXME We send a FIR every tot seconds */
4491 janus_videoroom_reqfir(participant, "Regular keyframe request");
4492 }
4493 }
4494 }
4495 }
4496 janus_videoroom_publisher_dereference_nodebug(participant);
4497}
4498
4499void janus_videoroom_incoming_rtcp(janus_plugin_session *handle, int video, char *buf, int len) {
4500 if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
4501 return;
4502 janus_videoroom_session *session = (janus_videoroom_session *)handle->plugin_handle;
4503 if(!session) {
4504 JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
4505 return;
4506 }
4507 if(g_atomic_int_get(&session->destroyed))
4508 return;
4509 if(session->participant_type == janus_videoroom_p_type_subscriber) {
4510 /* A subscriber sent some RTCP, check what it is and if we need to forward it to the publisher */
4511 janus_videoroom_subscriber *s = (janus_videoroom_subscriber *)session->participant;
4512 if(s == NULL || g_atomic_int_get(&s->destroyed))
4513 return;
4514 if(!s->video)
4515 return; /* The only feedback we handle is video related anyway... */
4516 if(janus_rtcp_has_fir(buf, len)) {
4517 /* We got a FIR, forward it to the publisher */
4518 if(s->feed) {
4519 janus_videoroom_publisher *p = s->feed;
4520 if(p && p->session) {
4521 char rtcpbuf[20];
4522 janus_rtcp_fir((char *)&rtcpbuf, 20, &p->fir_seq);
4523 JANUS_LOG(LOG_VERB, "Got a FIR from a subscriber, forwarding it to %"SCNu64" (%s)\n", p->user_id, p->display ? p->display : "??");
4524 gateway->relay_rtcp(p->session->handle, 1, rtcpbuf, 20);
4525 /* Update the time of when we last sent a keyframe request */
4526 p->fir_latest = janus_get_monotonic_time();
4527 }
4528 }
4529 }
4530 if(janus_rtcp_has_pli(buf, len)) {
4531 /* We got a PLI, forward it to the publisher */
4532 if(s->feed) {
4533 janus_videoroom_publisher *p = s->feed;
4534 if(p && p->session) {
4535 char rtcpbuf[12];
4536 janus_rtcp_pli((char *)&rtcpbuf, 12);
4537 JANUS_LOG(LOG_VERB, "Got a PLI from a subscriber, forwarding it to %"SCNu64" (%s)\n", p->user_id, p->display ? p->display : "??");
4538 gateway->relay_rtcp(p->session->handle, 1, rtcpbuf, 12);
4539 /* Update the time of when we last sent a keyframe request */
4540 p->fir_latest = janus_get_monotonic_time();
4541 }
4542 }
4543 }
4544 uint32_t bitrate = janus_rtcp_get_remb(buf, len);
4545 if(bitrate > 0) {
4546 /* FIXME We got a REMB from this subscriber, should we do something about it? */
4547 }
4548 }
4549}
4550
4551void janus_videoroom_incoming_data(janus_plugin_session *handle, char *label, char *buf, int len) {
4552 if(handle == NULL || g_atomic_int_get(&handle->stopped) || g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized) || !gateway)
4553 return;
4554 if(buf == NULL || len <= 0)
4555 return;
4556 janus_videoroom_session *session = (janus_videoroom_session *)handle->plugin_handle;
4557 if(!session || g_atomic_int_get(&session->destroyed) || session->participant_type != janus_videoroom_p_type_publisher)
4558 return;
4559 janus_videoroom_publisher *participant = janus_videoroom_session_get_publisher_nodebug(session);
4560 if(participant == NULL)
4561 return;
4562 if(g_atomic_int_get(&participant->destroyed) || !participant->data_active || participant->kicked) {
4563 janus_videoroom_publisher_dereference_nodebug(participant);
4564 return;
4565 }
4566 /* Any forwarder involved? */
4567 janus_mutex_lock(&participant->rtp_forwarders_mutex);
4568 /* Forward RTP to the appropriate port for the rtp_forwarders associated with this publisher, if there are any */
4569 GHashTableIter iter;
4570 gpointer value;
4571 g_hash_table_iter_init(&iter, participant->rtp_forwarders);
4572 while(participant->udp_sock > 0 && g_hash_table_iter_next(&iter, NULL, &value)) {
4573 janus_videoroom_rtp_forwarder* rtp_forward = (janus_videoroom_rtp_forwarder*)value;
4574 if(rtp_forward->is_data) {
4575 if(sendto(participant->udp_sock, buf, len, 0, (struct sockaddr*)&rtp_forward->serv_addr, sizeof(rtp_forward->serv_addr)) < 0) {
4576 JANUS_LOG(LOG_HUGE, "Error forwarding data packet for %s... %s (len=%d)...\n",
4577 participant->display, strerror(errno), len);
4578 }
4579 }
4580 }
4581 janus_mutex_unlock(&participant->rtp_forwarders_mutex);
4582 /* Get a string out of the data */
4583 char *text = g_malloc(len+1);
4584 memcpy(text, buf, len);
4585 *(text+len) = '\0';
4586 JANUS_LOG(LOG_VERB, "Got a DataChannel message (%zu bytes) to forward: %s\n", strlen(text), text);
4587 /* Save the message if we're recording */
4588 janus_recorder_save_frame(participant->drc, text, strlen(text));
4589 /* Relay to all subscribers */
4590 janus_mutex_lock_nodebug(&participant->subscribers_mutex);
4591 g_slist_foreach(participant->subscribers, janus_videoroom_relay_data_packet, text);
4592 janus_mutex_unlock_nodebug(&participant->subscribers_mutex);
4593 g_free(text);
4594 janus_videoroom_publisher_dereference_nodebug(participant);
4595}
4596
4597void janus_videoroom_slow_link(janus_plugin_session *handle, int uplink, int video) {
4598 /* The core is informing us that our peer got too many NACKs, are we pushing media too hard? */
4599 if(handle == NULL || g_atomic_int_get(&handle->stopped) || g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized) || !gateway)
4600 return;
4601 janus_mutex_lock(&sessions_mutex);
4602 janus_videoroom_session *session = janus_videoroom_lookup_session(handle);
4603 if(!session || g_atomic_int_get(&session->destroyed) || !session->participant) {
4604 janus_mutex_unlock(&sessions_mutex);
4605 return;
4606 }
4607 janus_refcount_increase(&session->ref);
4608 janus_mutex_unlock(&sessions_mutex);
4609 /* Check if it's an uplink (publisher) or downlink (viewer) issue */
4610 if(session->participant_type == janus_videoroom_p_type_publisher) {
4611 if(!uplink) {
4612 janus_videoroom_publisher *publisher = janus_videoroom_session_get_publisher(session);
4613 if(publisher == NULL || g_atomic_int_get(&publisher->destroyed)) {
4614 janus_refcount_decrease(&session->ref);
4615 janus_refcount_decrease(&publisher->ref);
4616 return;
4617 }
4618 /* Send an event on the handle to notify the application: it's
4619 * up to the application to then choose a policy and enforce it */
4620 json_t *event = json_object();
4621 json_object_set_new(event, "videoroom", json_string("slow_link"));
4622 /* Also add info on what the current bitrate cap is */
4623 uint32_t bitrate = publisher->bitrate;
4624 json_object_set_new(event, "current-bitrate", json_integer(bitrate));
4625 gateway->push_event(session->handle, &janus_videoroom_plugin, NULL, event, NULL);
4626 json_decref(event);
4627 janus_refcount_decrease(&publisher->ref);
4628 } else {
4629 JANUS_LOG(LOG_WARN, "Got a slow uplink on a VideoRoom publisher? Weird, because it doesn't receive media...\n");
4630 }
4631 } else if(session->participant_type == janus_videoroom_p_type_subscriber) {
4632 if(uplink) {
4633 janus_videoroom_subscriber *viewer = (janus_videoroom_subscriber *)session->participant;
4634 if(viewer == NULL || g_atomic_int_get(&viewer->destroyed)) {
4635 janus_refcount_decrease(&session->ref);
4636 return;
4637 }
4638 /* Send an event on the handle to notify the application: it's
4639 * up to the application to then choose a policy and enforce it */
4640 json_t *event = json_object();
4641 json_object_set_new(event, "videoroom", json_string("slow_link"));
4642 gateway->push_event(session->handle, &janus_videoroom_plugin, NULL, event, NULL);
4643 json_decref(event);
4644 } else {
4645 JANUS_LOG(LOG_WARN, "Got a slow downlink on a VideoRoom viewer? Weird, because it doesn't send media...\n");
4646 }
4647 }
4648 janus_refcount_decrease(&session->ref);
4649}
4650
4651static void janus_videoroom_recorder_create(janus_videoroom_publisher *participant, gboolean audio, gboolean video, gboolean data) {
4652 char filename[255];
4653 gint64 now = janus_get_real_time();
4654 if(audio && participant->arc == NULL) {
4655 memset(filename, 0, 255);
4656 if(participant->recording_base) {
4657 /* Use the filename and path we have been provided */
4658 g_snprintf(filename, 255, "%s-audio", participant->recording_base);
4659 participant->arc = janus_recorder_create(participant->room->rec_dir,
4660 janus_audiocodec_name(participant->acodec), filename);
4661 if(participant->arc == NULL) {
4662 JANUS_LOG(LOG_ERR, "Couldn't open an audio recording file for this publisher!\n");
4663 }
4664 } else {
4665 /* Build a filename */
4666 g_snprintf(filename, 255, "videoroom-%"SCNu64"-user-%"SCNu64"-%"SCNi64"-audio",
4667 participant->room_id, participant->user_id, now);
4668 participant->arc = janus_recorder_create(participant->room->rec_dir,
4669 janus_audiocodec_name(participant->acodec), filename);
4670 if(participant->arc == NULL) {
4671 JANUS_LOG(LOG_ERR, "Couldn't open an audio recording file for this publisher!\n");
4672 }
4673 }
4674 }
4675 if(video && participant->vrc == NULL) {
4676 janus_rtp_switching_context_reset(&participant->rec_ctx);
4677 janus_rtp_simulcasting_context_reset(&participant->rec_simctx);
4678 participant->rec_simctx.substream_target = 2;
4679 participant->rec_simctx.templayer_target = 2;
4680 memset(filename, 0, 255);
4681 if(participant->recording_base) {
4682 /* Use the filename and path we have been provided */
4683 g_snprintf(filename, 255, "%s-video", participant->recording_base);
4684 participant->vrc = janus_recorder_create(participant->room->rec_dir,
4685 janus_videocodec_name(participant->vcodec), filename);
4686 if(participant->vrc == NULL) {
4687 JANUS_LOG(LOG_ERR, "Couldn't open an video recording file for this publisher!\n");
4688 }
4689 } else {
4690 /* Build a filename */
4691 g_snprintf(filename, 255, "videoroom-%"SCNu64"-user-%"SCNu64"-%"SCNi64"-video",
4692 participant->room_id, participant->user_id, now);
4693 participant->vrc = janus_recorder_create(participant->room->rec_dir,
4694 janus_videocodec_name(participant->vcodec), filename);
4695 if(participant->vrc == NULL) {
4696 JANUS_LOG(LOG_ERR, "Couldn't open an video recording file for this publisher!\n");
4697 }
4698 }
4699 }
4700 if(data && participant->drc == NULL) {
4701 memset(filename, 0, 255);
4702 if(participant->recording_base) {
4703 /* Use the filename and path we have been provided */
4704 g_snprintf(filename, 255, "%s-data", participant->recording_base);
4705 participant->drc = janus_recorder_create(participant->room->rec_dir,
4706 "text", filename);
4707 if(participant->drc == NULL) {
4708 JANUS_LOG(LOG_ERR, "Couldn't open an data recording file for this publisher!\n");
4709 }
4710 } else {
4711 /* Build a filename */
4712 g_snprintf(filename, 255, "videoroom-%"SCNu64"-user-%"SCNu64"-%"SCNi64"-data",
4713 participant->room_id, participant->user_id, now);
4714 participant->drc = janus_recorder_create(participant->room->rec_dir,
4715 "text", filename);
4716 if(participant->drc == NULL) {
4717 JANUS_LOG(LOG_ERR, "Couldn't open an data recording file for this publisher!\n");
4718 }
4719 }
4720 }
4721}
4722
4723static void janus_videoroom_recorder_close(janus_videoroom_publisher *participant) {
4724 if(participant->arc) {
4725 janus_recorder *rc = participant->arc;
4726 participant->arc = NULL;
4727 janus_recorder_close(rc);
4728 JANUS_LOG(LOG_INFO, "Closed audio recording %s\n", rc->filename ? rc->filename : "??");
4729 janus_recorder_destroy(rc);
4730 }
4731 if(participant->vrc) {
4732 janus_recorder *rc = participant->vrc;
4733 participant->vrc = NULL;
4734 janus_recorder_close(rc);
4735 JANUS_LOG(LOG_INFO, "Closed video recording %s\n", rc->filename ? rc->filename : "??");
4736 janus_recorder_destroy(rc);
4737 }
4738 if(participant->drc) {
4739 janus_recorder *rc = participant->drc;
4740 participant->drc = NULL;
4741 janus_recorder_close(rc);
4742 JANUS_LOG(LOG_INFO, "Closed data recording %s\n", rc->filename ? rc->filename : "??");
4743 janus_recorder_destroy(rc);
4744 }
4745}
4746
4747void janus_videoroom_hangup_media(janus_plugin_session *handle) {
4748 JANUS_LOG(LOG_INFO, "[%s-%p] No WebRTC media anymore; %p %p\n", JANUS_VIDEOROOM_PACKAGE, handle, handle->gateway_handle, handle->plugin_handle);
4749 janus_mutex_lock(&sessions_mutex);
4750 janus_videoroom_hangup_media_internal(handle);
4751 janus_mutex_unlock(&sessions_mutex);
4752}
4753
4754static void janus_videoroom_hangup_subscriber(janus_videoroom_subscriber * s) {
4755 /* Already hung up */
4756 if (!s->feed) {
4757 JANUS_LOG(LOG_INFO, "videroom_hangup_subscriber NO FEED %p \n", &s->ref);
4758 return;
4759 } else {
4760 JANUS_LOG(LOG_INFO, "videroom_hangup_subscriber %p \n", &s->ref);
4761 }
4762 /* Check if the owner needs to be cleaned up */
4763 if(s->pvt_id > 0 && s->room != NULL) {
4764 janus_mutex_lock(&s->room->mutex);
4765 janus_videoroom_publisher *owner = g_hash_table_lookup(s->room->private_ids, GUINT_TO_POINTER(s->pvt_id));
4766 if(owner != NULL) {
4767 janus_mutex_lock(&owner->subscribers_mutex);
4768 /* Note: we should refcount these subscription-publisher mappings as well */
4769 owner->subscriptions = g_slist_remove(owner->subscriptions, s);
4770 janus_mutex_unlock(&owner->subscribers_mutex);
4771 }
4772 janus_mutex_unlock(&s->room->mutex);
4773 }
4774 /* TODO: are we sure this is okay as other handlers use feed directly without synchronization */
4775 if(s->feed)
4776 g_clear_pointer(&s->feed, janus_videoroom_publisher_dereference_by_subscriber);
4777 if(s->close_pc) {
4778 if(s->room)
4779 g_clear_pointer(&s->room, janus_videoroom_room_dereference);
4780 if(s->session)
4781 gateway->close_pc(s->session->handle);
4782 /* Done with the subscriber and free its reference */
4783 janus_refcount_decrease(&s->ref);
4784 }
4785}
4786
4787static void janus_videoroom_hangup_media_internal(janus_plugin_session *handle) {
4788 if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
4789 return;
4790 janus_videoroom_session *session = janus_videoroom_lookup_session(handle);
4791 if(!session) {
4792 JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
4793 return;
4794 }
4795 session->started = FALSE;
4796 if(g_atomic_int_get(&session->destroyed))
4797 return;
4798 if(!g_atomic_int_compare_and_exchange(&session->hangingup, 0, 1))
4799 return;
4800 /* Send an event to the browser and tell the PeerConnection is over */
4801 if(session->participant_type == janus_videoroom_p_type_publisher) {
4802 /* This publisher just 'unpublished' */
4803 janus_videoroom_publisher *participant = janus_videoroom_session_get_publisher(session);
4804 /* Get rid of the recorders, if available */
4805 janus_mutex_lock(&participant->rec_mutex);
4806 g_free(participant->recording_base);
4807 participant->recording_base = NULL;
4808 janus_videoroom_recorder_close(participant);
4809 janus_mutex_unlock(&participant->rec_mutex);
4810 /* Use subscribers_mutex to protect fields used in janus_videoroom_incoming_rtp */
4811 janus_mutex_lock(&participant->subscribers_mutex);
4812 g_free(participant->sdp);
4813 participant->sdp = NULL;
4814 participant->firefox = FALSE;
4815 participant->audio_active = FALSE;
4816 participant->video_active = FALSE;
4817 participant->data_active = FALSE;
4818 participant->audio_active_packets = 0;
4819 participant->audio_dBov_sum = 0;
4820 participant->audio_dBov_level = 0;
4821 participant->talking = FALSE;
4822 participant->remb_startup = 4;
4823 participant->remb_latest = 0;
4824 participant->fir_latest = 0;
4825 participant->fir_seq = 0;
4826 int i=0;
4827 for(i=0; i<3; i++) {
4828 participant->ssrc[i] = 0;
4829 g_free(participant->rid[i]);
4830 participant->rid[i] = NULL;
4831 }
4832 GSList *subscribers = participant->subscribers;
4833 participant->subscribers = NULL;
4834 janus_mutex_unlock(&participant->subscribers_mutex);
4835 /* Hangup all subscribers */
4836 while(subscribers) {
4837 janus_videoroom_subscriber *s = (janus_videoroom_subscriber *)subscribers->data;
4838 subscribers = g_slist_remove(subscribers, s);
4839 if(s) {
4840 janus_videoroom_hangup_subscriber(s);
4841 }
4842 }
4843 janus_videoroom_leave_or_unpublish(participant, FALSE, FALSE);
4844 janus_refcount_decrease(&participant->ref);
4845 } else if(session->participant_type == janus_videoroom_p_type_subscriber) {
4846 /* Get rid of subscriber */
4847 janus_videoroom_subscriber *subscriber = (janus_videoroom_subscriber *)session->participant;
4848 if(subscriber) {
4849 subscriber->paused = TRUE;
4850 janus_videoroom_publisher *publisher = subscriber->feed;
4851 /* It is safe to use feed as the only other place sets feed to NULL
4852 is in this function and accessing to this function is synchronized
4853 by sessions_mutex */
4854 if(publisher != NULL) {
4855 /* Also notify event handlers */
4856 if(notify_events && gateway->events_is_enabled()) {
4857 json_t *info = json_object();
4858 json_object_set_new(info, "event", json_string("unsubscribed"));
4859 json_object_set_new(info, "room", json_integer(publisher->room_id));
4860 json_object_set_new(info, "feed", json_integer(publisher->user_id));
4861 gateway->notify_event(&janus_videoroom_plugin, session->handle, info);
4862 }
4863 janus_mutex_lock(&publisher->subscribers_mutex);
4864 publisher->subscribers = g_slist_remove(publisher->subscribers, subscriber);
4865 janus_mutex_unlock(&publisher->subscribers_mutex);
4866 janus_videoroom_hangup_subscriber(subscriber);
4867 }
4868 }
4869 /* TODO Should we close the handle as well? */
4870 }
4871 g_atomic_int_set(&session->hangingup, 0);
4872}
4873
4874/* Thread to handle incoming messages */
4875static void *janus_videoroom_handler(void *data) {
4876 JANUS_LOG(LOG_VERB, "Joining VideoRoom handler thread\n");
4877 janus_videoroom_message *msg = NULL;
4878 int error_code = 0;
4879 char error_cause[512];
4880 json_t *root = NULL;
4881 while(g_atomic_int_get(&initialized) && !g_atomic_int_get(&stopping)) {
4882 msg = g_async_queue_pop(messages);
4883 if(msg == &exit_message)
4884 break;
4885 if(msg->handle == NULL) {
4886 janus_videoroom_message_free(msg);
4887 continue;
4888 }
4889 janus_videoroom *videoroom = NULL;
4890 janus_videoroom_publisher *participant = NULL;
4891 janus_mutex_lock(&sessions_mutex);
4892 janus_videoroom_session *session = janus_videoroom_lookup_session(msg->handle);
4893 if(!session) {
4894 janus_mutex_unlock(&sessions_mutex);
4895 JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
4896 janus_videoroom_message_free(msg);
4897 continue;
4898 }
4899 if(g_atomic_int_get(&session->destroyed)) {
4900 janus_mutex_unlock(&sessions_mutex);
4901 janus_videoroom_message_free(msg);
4902 continue;
4903 }
4904 janus_mutex_unlock(&sessions_mutex);
4905 /* Handle request */
4906 error_code = 0;
4907 root = NULL;
4908 if(msg->message == NULL) {
4909 JANUS_LOG(LOG_ERR, "No message??\n");
4910 error_code = JANUS_VIDEOROOM_ERROR_NO_MESSAGE;
4911 g_snprintf(error_cause, 512, "%s", "No message??");
4912 goto error;
4913 }
4914 root = msg->message;
4915 /* Get the request first */
4916 JANUS_VALIDATE_JSON_OBJECT(root, request_parameters,
4917 error_code, error_cause, TRUE,
4918 JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT);
4919 if(error_code != 0)
4920 goto error;
4921 json_t *request = json_object_get(root, "request");
4922 const char *request_text = json_string_value(request);
4923 json_t *event = NULL;
4924 gboolean sdp_update = FALSE;
4925 if(json_object_get(msg->jsep, "update") != NULL)
4926 sdp_update = json_is_true(json_object_get(msg->jsep, "update"));
4927 /* 'create' and 'destroy' are handled synchronously: what kind of participant is this session referring to? */
4928 if(session->participant_type == janus_videoroom_p_type_none) {
4929 JANUS_LOG(LOG_VERB, "Configuring new participant\n");
4930 /* Not configured yet, we need to do this now */
4931 if(strcasecmp(request_text, "join") && strcasecmp(request_text, "joinandconfigure")) {
4932 JANUS_LOG(LOG_ERR, "Invalid request on unconfigured participant\n");
4933 error_code = JANUS_VIDEOROOM_ERROR_JOIN_FIRST;
4934 g_snprintf(error_cause, 512, "Invalid request on unconfigured participant");
4935 goto error;
4936 }
4937 JANUS_VALIDATE_JSON_OBJECT(root, join_parameters,
4938 error_code, error_cause, TRUE,
4939 JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT);
4940 if(error_code != 0)
4941 goto error;
4942 janus_mutex_lock(&rooms_mutex);
4943 error_code = janus_videoroom_access_room(root, FALSE, TRUE, &videoroom, error_cause, sizeof(error_cause));
4944 if(error_code != 0) {
4945 janus_mutex_unlock(&rooms_mutex);
4946 goto error;
4947 }
4948 janus_refcount_increase(&videoroom->ref);
4949 janus_mutex_lock(&videoroom->mutex);
4950 janus_mutex_unlock(&rooms_mutex);
4951 json_t *ptype = json_object_get(root, "ptype");
4952 const char *ptype_text = json_string_value(ptype);
4953 if(!strcasecmp(ptype_text, "publisher")) {
4954 JANUS_LOG(LOG_VERB, "Configuring new publisher\n");
4955 JANUS_VALIDATE_JSON_OBJECT(root, publisher_parameters,
4956 error_code, error_cause, TRUE,
4957 JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT);
4958 if(error_code != 0) {
4959 janus_mutex_unlock(&videoroom->mutex);
4960 janus_refcount_decrease(&videoroom->ref);
4961 goto error;
4962 }
4963 /* A token might be required to join */
4964 if(videoroom->check_allowed) {
4965 json_t *token = json_object_get(root, "token");
4966 const char *token_text = token ? json_string_value(token) : NULL;
4967 if(token_text == NULL || g_hash_table_lookup(videoroom->allowed, token_text) == NULL) {
4968 janus_mutex_unlock(&videoroom->mutex);
4969 janus_refcount_decrease(&videoroom->ref);
4970 JANUS_LOG(LOG_ERR, "Unauthorized (not in the allowed list)\n");
4971 error_code = JANUS_VIDEOROOM_ERROR_UNAUTHORIZED;
4972 g_snprintf(error_cause, 512, "Unauthorized (not in the allowed list)");
4973 goto error;
4974 }
4975 }
4976 json_t *display = json_object_get(root, "display");
4977 const char *display_text = display ? json_string_value(display) : NULL;
4978 guint64 user_id = 0;
4979 json_t *id = json_object_get(root, "id");
4980 if(id) {
4981 user_id = json_integer_value(id);
4982 if(g_hash_table_lookup(videoroom->participants, &user_id) != NULL) {
4983 janus_mutex_unlock(&videoroom->mutex);
4984 janus_refcount_decrease(&videoroom->ref);
4985 /* User ID already taken */
4986 JANUS_LOG(LOG_ERR, "User ID %"SCNu64" already exists\n", user_id);
4987 error_code = JANUS_VIDEOROOM_ERROR_ID_EXISTS;
4988 g_snprintf(error_cause, 512, "User ID %"SCNu64" already exists", user_id);
4989 goto error;
4990 }
4991 }
4992 if(user_id == 0) {
4993 /* Generate a random ID */
4994 while(user_id == 0) {
4995 user_id = janus_random_uint64();
4996 if(g_hash_table_lookup(videoroom->participants, &user_id) != NULL) {
4997 /* User ID already taken, try another one */
4998 user_id = 0;
4999 }
5000 }
5001 }
5002 JANUS_LOG(LOG_VERB, " -- Publisher ID: %"SCNu64"\n", user_id);
5003 /* Process the request */
5004 json_t *audio = NULL, *video = NULL, *data = NULL,
5005 *bitrate = NULL, *record = NULL, *recfile = NULL;
5006 if(!strcasecmp(request_text, "joinandconfigure")) {
5007 /* Also configure (or publish a new feed) audio/video/bitrate for this new publisher */
5008 /* join_parameters were validated earlier. */
5009 audio = json_object_get(root, "audio");
5010 video = json_object_get(root, "video");
5011 data = json_object_get(root, "data");
5012 bitrate = json_object_get(root, "bitrate");
5013 record = json_object_get(root, "record");
5014 recfile = json_object_get(root, "filename");
5015 }
5016 janus_videoroom_publisher *publisher = g_malloc0(sizeof(janus_videoroom_publisher));
5017 publisher->session = session;
5018 publisher->room_id = videoroom->room_id;
5019 publisher->room = videoroom;
5020 videoroom = NULL;
5021 publisher->user_id = user_id;
5022 publisher->display = display_text ? g_strdup(display_text) : NULL;
5023 publisher->sdp = NULL; /* We'll deal with this later */
5024 publisher->audio = FALSE; /* We'll deal with this later */
5025 publisher->video = FALSE; /* We'll deal with this later */
5026 publisher->data = FALSE; /* We'll deal with this later */
5027 publisher->acodec = JANUS_AUDIOCODEC_NONE; /* We'll deal with this later */
5028 publisher->vcodec = JANUS_VIDEOCODEC_NONE; /* We'll deal with this later */
5029 publisher->audio_active = TRUE;
5030 publisher->video_active = TRUE;
5031 publisher->data_active = TRUE;
5032 publisher->recording_active = FALSE;
5033 publisher->recording_base = NULL;
5034 publisher->arc = NULL;
5035 publisher->vrc = NULL;
5036 publisher->drc = NULL;
5037 janus_mutex_init(&publisher->rec_mutex);
5038 publisher->firefox = FALSE;
5039 publisher->bitrate = publisher->room->bitrate;
5040 publisher->subscribers = NULL;
5041 publisher->subscriptions = NULL;
5042 janus_mutex_init(&publisher->subscribers_mutex);
5043 publisher->audio_pt = -1; /* We'll deal with this later */
5044 publisher->video_pt = -1; /* We'll deal with this later */
5045 publisher->audio_level_extmap_id = 0;
5046 publisher->video_orient_extmap_id = 0;
5047 publisher->playout_delay_extmap_id = 0;
5048 publisher->remb_startup = 4;
5049 publisher->remb_latest = 0;
5050 publisher->fir_latest = 0;
5051 publisher->fir_seq = 0;
5052 janus_mutex_init(&publisher->rtp_forwarders_mutex);
5053 publisher->rtp_forwarders = g_hash_table_new_full(NULL, NULL, NULL, (GDestroyNotify)janus_videoroom_rtp_forwarder_destroy);
5054 publisher->srtp_contexts = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, (GDestroyNotify)janus_videoroom_srtp_context_free);
5055 publisher->udp_sock = -1;
5056 /* Finally, generate a private ID: this is only needed in case the participant
5057 * wants to allow the plugin to know which subscriptions belong to them */
5058 publisher->pvt_id = 0;
5059 while(publisher->pvt_id == 0) {
5060 publisher->pvt_id = janus_random_uint32();
5061 if(g_hash_table_lookup(publisher->room->private_ids, GUINT_TO_POINTER(publisher->pvt_id)) != NULL) {
5062 /* Private ID already taken, try another one */
5063 publisher->pvt_id = 0;
5064 }
5065 }
5066 g_hash_table_insert(publisher->room->private_ids, GUINT_TO_POINTER(publisher->pvt_id), publisher);
5067 g_atomic_int_set(&publisher->destroyed, 0);
5068 janus_refcount_init(&publisher->ref, janus_videoroom_publisher_free);
5069 /* In case we also wanted to configure */
5070 if(audio) {
5071 publisher->audio_active = json_is_true(audio);
5072 JANUS_LOG(LOG_VERB, "Setting audio property: %s (room %"SCNu64", user %"SCNu64")\n", publisher->audio_active ? "true" : "false", publisher->room_id, publisher->user_id);
5073 }
5074 if(video) {
5075 publisher->video_active = json_is_true(video);
5076 JANUS_LOG(LOG_VERB, "Setting video property: %s (room %"SCNu64", user %"SCNu64")\n", publisher->video_active ? "true" : "false", publisher->room_id, publisher->user_id);
5077 }
5078 if(data) {
5079 publisher->data_active = json_is_true(data);
5080 JANUS_LOG(LOG_VERB, "Setting data property: %s (room %"SCNu64", user %"SCNu64")\n", publisher->data_active ? "true" : "false", publisher->room_id, publisher->user_id);
5081 }
5082 if(bitrate) {
5083 publisher->bitrate = json_integer_value(bitrate);
5084 JANUS_LOG(LOG_VERB, "Setting video bitrate: %"SCNu32" (room %"SCNu64", user %"SCNu64")\n", publisher->bitrate, publisher->room_id, publisher->user_id);
5085 }
5086 if(record) {
5087 publisher->recording_active = json_is_true(record);
5088 JANUS_LOG(LOG_VERB, "Setting record property: %s (room %"SCNu64", user %"SCNu64")\n", publisher->recording_active ? "true" : "false", publisher->room_id, publisher->user_id);
5089 }
5090 if(recfile) {
5091 publisher->recording_base = g_strdup(json_string_value(recfile));
5092 JANUS_LOG(LOG_VERB, "Setting recording basename: %s (room %"SCNu64", user %"SCNu64")\n", publisher->recording_base, publisher->room_id, publisher->user_id);
5093 }
5094 /* Done */
5095 janus_mutex_lock(&session->mutex);
5096 session->participant_type = janus_videoroom_p_type_publisher;
5097 session->participant = publisher;
5098 janus_mutex_unlock(&session->mutex);
5099 /* Return a list of all available publishers (those with an SDP available, that is) */
5100 json_t *list = json_array(), *attendees = NULL;
5101 if(publisher->room->notify_joining)
5102 attendees = json_array();
5103 GHashTableIter iter;
5104 gpointer value;
5105 janus_refcount_increase(&publisher->ref);
5106 g_hash_table_insert(publisher->room->participants, janus_uint64_dup(publisher->user_id), publisher);
5107 g_hash_table_iter_init(&iter, publisher->room->participants);
5108 while (!g_atomic_int_get(&publisher->room->destroyed) && g_hash_table_iter_next(&iter, NULL, &value)) {
5109 janus_videoroom_publisher *p = value;
5110 if(p == publisher || !p->sdp || !p->session->started) {
5111 /* Check if we're also notifying normal joins and not just publishers */
5112 if(p != publisher && publisher->room->notify_joining) {
5113 json_t *al = json_object();
5114 json_object_set_new(al, "id", json_integer(p->user_id));
5115 if(p->display)
5116 json_object_set_new(al, "display", json_string(p->display));
5117 json_array_append_new(attendees, al);
5118 }
5119 continue;
5120 }
5121 json_t *pl = json_object();
5122 json_object_set_new(pl, "id", json_integer(p->user_id));
5123 if(p->display)
5124 json_object_set_new(pl, "display", json_string(p->display));
5125 if(p->audio)
5126 json_object_set_new(pl, "audio_codec", json_string(janus_audiocodec_name(p->acodec)));
5127 if(p->video)
5128 json_object_set_new(pl, "video_codec", json_string(janus_videocodec_name(p->vcodec)));
5129 if(p->ssrc[0] || p->rid[0])
5130 json_object_set_new(pl, "simulcast", json_true());
5131 if(p->audio_level_extmap_id > 0)
5132 json_object_set_new(pl, "talking", p->talking ? json_true() : json_false());
5133 json_array_append_new(list, pl);
5134 }
5135 event = json_object();
5136 json_object_set_new(event, "videoroom", json_string("joined"));
5137 json_object_set_new(event, "room", json_integer(publisher->room->room_id));
5138 json_object_set_new(event, "description", json_string(publisher->room->room_name));
5139 json_object_set_new(event, "id", json_integer(user_id));
5140 json_object_set_new(event, "private_id", json_integer(publisher->pvt_id));
5141 json_object_set_new(event, "publishers", list);
5142 if(attendees != NULL)
5143 json_object_set_new(event, "attendees", attendees);
5144 /* See if we need to notify about a new participant joined the room (by default, we don't). */
5145 janus_videoroom_participant_joining(publisher);
5146
5147 /* Also notify event handlers */
5148 if(notify_events && gateway->events_is_enabled()) {
5149 json_t *info = json_object();
5150 json_object_set_new(info, "event", json_string("joined"));
5151 json_object_set_new(info, "room", json_integer(publisher->room->room_id));
5152 json_object_set_new(info, "id", json_integer(user_id));
5153 json_object_set_new(info, "private_id", json_integer(publisher->pvt_id));
5154 if(display_text != NULL)
5155 json_object_set_new(info, "display", json_string(display_text));
5156 gateway->notify_event(&janus_videoroom_plugin, session->handle, info);
5157 }
5158 janus_mutex_unlock(&publisher->room->mutex);
5159 } else if(!strcasecmp(ptype_text, "subscriber") || !strcasecmp(ptype_text, "listener")) {
5160 JANUS_LOG(LOG_VERB, "Configuring new subscriber\n");
5161 gboolean legacy = !strcasecmp(ptype_text, "listener");
5162 if(legacy) {
5163 JANUS_LOG(LOG_WARN, "Subscriber is using the legacy 'listener' ptype\n");
5164 }
5165 /* This is a new subscriber */
5166 JANUS_VALIDATE_JSON_OBJECT(root, subscriber_parameters,
5167 error_code, error_cause, TRUE,
5168 JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT);
5169 if(error_code != 0) {
5170 janus_mutex_unlock(&videoroom->mutex);
5171 goto error;
5172 }
5173 json_t *feed = json_object_get(root, "feed");
5174 guint64 feed_id = json_integer_value(feed);
5175 json_t *pvt = json_object_get(root, "private_id");
5176 guint64 pvt_id = json_integer_value(pvt);
5177 json_t *cpc = json_object_get(root, "close_pc");
5178 gboolean close_pc = cpc ? json_is_true(cpc) : TRUE;
5179 json_t *audio = json_object_get(root, "audio");
5180 json_t *video = json_object_get(root, "video");
5181 json_t *data = json_object_get(root, "data");
5182 json_t *offer_audio = json_object_get(root, "offer_audio");
5183 json_t *offer_video = json_object_get(root, "offer_video");
5184 json_t *offer_data = json_object_get(root, "offer_data");
5185 json_t *spatial = json_object_get(root, "spatial_layer");
5186 json_t *sc_substream = json_object_get(root, "substream");
5187 if(json_integer_value(spatial) < 0 || json_integer_value(spatial) > 2 ||
5188 json_integer_value(sc_substream) < 0 || json_integer_value(sc_substream) > 2) {
5189 JANUS_LOG(LOG_ERR, "Invalid element (substream/spatial_layer should be 0, 1 or 2)\n");
5190 error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
5191 g_snprintf(error_cause, 512, "Invalid value (substream/spatial_layer should be 0, 1 or 2)");
5192 janus_mutex_unlock(&videoroom->mutex);
5193 goto error;
5194 }
5195 json_t *temporal = json_object_get(root, "temporal_layer");
5196 json_t *sc_temporal = json_object_get(root, "temporal");
5197 if(json_integer_value(temporal) < 0 || json_integer_value(temporal) > 2 ||
5198 json_integer_value(sc_temporal) < 0 || json_integer_value(sc_temporal) > 2) {
5199 JANUS_LOG(LOG_ERR, "Invalid element (temporal/temporal_layer should be 0, 1 or 2)\n");
5200 error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
5201 g_snprintf(error_cause, 512, "Invalid value (temporal/temporal_layer should be 0, 1 or 2)");
5202 janus_mutex_unlock(&videoroom->mutex);
5203 goto error;
5204 }
5205 janus_videoroom_publisher *owner = NULL;
5206 janus_videoroom_publisher *publisher = g_hash_table_lookup(videoroom->participants, &feed_id);
5207 if(publisher == NULL || g_atomic_int_get(&publisher->destroyed) || publisher->sdp == NULL) {
5208 JANUS_LOG(LOG_ERR, "No such feed (%"SCNu64")\n", feed_id);
5209 error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_FEED;
5210 g_snprintf(error_cause, 512, "No such feed (%"SCNu64")", feed_id);
5211 janus_mutex_unlock(&videoroom->mutex);
5212 goto error;
5213 } else {
5214 /* Increase the refcount before unlocking so that nobody can remove and free the publisher in the meantime. */
5215 janus_refcount_increase(&publisher->ref);
5216 janus_refcount_increase(&publisher->session->ref);
5217 /* First of all, let's check if this room requires valid private_id values */
5218 if(videoroom->require_pvtid) {
5219 /* It does, let's make sure this subscription complies */
5220 owner = g_hash_table_lookup(videoroom->private_ids, GUINT_TO_POINTER(pvt_id));
5221 if(pvt_id == 0 || owner == NULL) {
5222 JANUS_LOG(LOG_ERR, "Unauthorized (this room requires a valid private_id)\n");
5223 error_code = JANUS_VIDEOROOM_ERROR_UNAUTHORIZED;
5224 g_snprintf(error_cause, 512, "Unauthorized (this room requires a valid private_id)");
5225 janus_mutex_unlock(&videoroom->mutex);
5226 goto error;
5227 }
5228 janus_refcount_increase(&owner->ref);
5229 janus_refcount_increase(&owner->session->ref);
5230 }
5231 janus_mutex_unlock(&videoroom->mutex);
5232 janus_videoroom_subscriber *subscriber = g_malloc0(sizeof(janus_videoroom_subscriber));
5233 subscriber->session = session;
5234 subscriber->room_id = videoroom->room_id;
5235 subscriber->room = videoroom;
5236 videoroom = NULL;
5237 subscriber->feed = publisher;
5238 subscriber->pvt_id = pvt_id;
5239 subscriber->close_pc = close_pc;
5240 /* Initialize the subscriber context */
5241 janus_rtp_switching_context_reset(&subscriber->context);
5242 subscriber->audio_offered = offer_audio ? json_is_true(offer_audio) : TRUE; /* True by default */
5243 subscriber->video_offered = offer_video ? json_is_true(offer_video) : TRUE; /* True by default */
5244 subscriber->data_offered = offer_data ? json_is_true(offer_data) : TRUE; /* True by default */
5245 if((!publisher->audio || !subscriber->audio_offered) &&
5246 (!publisher->video || !subscriber->video_offered) &&
5247 (!publisher->data || !subscriber->data_offered)) {
5248 g_free(subscriber);
5249 if (owner) {
5250 janus_refcount_decrease(&owner->session->ref);
5251 janus_refcount_decrease(&owner->ref);
5252 }
5253 janus_refcount_decrease(&publisher->session->ref);
5254 janus_refcount_decrease(&publisher->ref);
5255 JANUS_LOG(LOG_ERR, "Can't offer an SDP with no audio, video or data\n");
5256 error_code = JANUS_VIDEOROOM_ERROR_INVALID_SDP;
5257 g_snprintf(error_cause, 512, "Can't offer an SDP with no audio, video or data");
5258 goto error;
5259 }
5260 subscriber->audio = audio ? json_is_true(audio) : TRUE; /* True by default */
5261 if(!publisher->audio || !subscriber->audio_offered)
5262 subscriber->audio = FALSE; /* ... unless the publisher isn't sending any audio or we're skipping it */
5263 subscriber->video = video ? json_is_true(video) : TRUE; /* True by default */
5264 if(!publisher->video || !subscriber->video_offered)
5265 subscriber->video = FALSE; /* ... unless the publisher isn't sending any video or we're skipping it */
5266 subscriber->data = data ? json_is_true(data) : TRUE; /* True by default */
5267 if(!publisher->data || !subscriber->data_offered)
5268 subscriber->data = FALSE; /* ... unless the publisher isn't sending any data or we're skipping it */
5269 subscriber->paused = TRUE; /* We need an explicit start from the subscriber */
5270 g_atomic_int_set(&subscriber->destroyed, 0);
5271 janus_refcount_init(&subscriber->ref, janus_videoroom_subscriber_free);
5272 janus_refcount_increase(&subscriber->ref); /* The publisher references the new subscriber too */
5273 /* Check if a simulcasting-related request is involved */
5274 janus_rtp_simulcasting_context_reset(&subscriber->sim_context);
5275 subscriber->sim_context.rid_ext_id = publisher->rid_extmap_id;
5276 subscriber->sim_context.substream_target = sc_substream ? json_integer_value(sc_substream) : 2;
5277 subscriber->sim_context.templayer_target = sc_temporal ? json_integer_value(sc_temporal) : 2;
5278 janus_vp8_simulcast_context_reset(&subscriber->vp8_context);
5279 /* Check if a VP9 SVC-related request is involved */
5280 if(subscriber->room->do_svc) {
5281 subscriber->spatial_layer = -1;
5282 subscriber->target_spatial_layer = spatial ? json_integer_value(spatial) : 2;
5283 subscriber->temporal_layer = -1;
5284 subscriber->target_temporal_layer = temporal ? json_integer_value(temporal) : 2;
5285 }
5286 session->participant = subscriber;
5287 janus_mutex_lock(&publisher->subscribers_mutex);
5288 publisher->subscribers = g_slist_append(publisher->subscribers, subscriber);
5289 janus_mutex_unlock(&publisher->subscribers_mutex);
5290 if(owner != NULL) {
5291 /* Note: we should refcount these subscription-publisher mappings as well */
5292 janus_mutex_lock(&owner->subscribers_mutex);
5293 owner->subscriptions = g_slist_append(owner->subscriptions, subscriber);
5294 janus_mutex_unlock(&owner->subscribers_mutex);
5295 /* Done adding the subscription, owner is safe to be released */
5296 janus_refcount_decrease(&owner->session->ref);
5297 janus_refcount_decrease(&owner->ref);
5298 }
5299 event = json_object();
5300 json_object_set_new(event, "videoroom", json_string("attached"));
5301 json_object_set_new(event, "room", json_integer(subscriber->room_id));
5302 json_object_set_new(event, "id", json_integer(feed_id));
5303 if(publisher->display)
5304 json_object_set_new(event, "display", json_string(publisher->display));
5305 if(legacy)
5306 json_object_set_new(event, "warning", json_string("Deprecated use of 'listener' ptype, update to the new 'subscriber' ASAP"));
5307 session->participant_type = janus_videoroom_p_type_subscriber;
5308 JANUS_LOG(LOG_VERB, "Preparing JSON event as a reply\n");
5309 /* Negotiate by sending the selected publisher SDP back */
5310 janus_mutex_lock(&publisher->subscribers_mutex);
5311 if(publisher->sdp != NULL) {
5312 /* Check if there's something the original SDP has that we should remove */
5313 janus_sdp *offer = janus_sdp_parse(publisher->sdp, NULL, 0);
5314 subscriber->sdp = offer;
5315 session->sdp_version = 1;
5316 subscriber->sdp->o_version = session->sdp_version;
5317 if((publisher->audio && !subscriber->audio_offered) ||
5318 (publisher->video && !subscriber->video_offered) ||
5319 (publisher->data && !subscriber->data_offered)) {
5320 JANUS_LOG(LOG_VERB, "Munging SDP offer to adapt it to the subscriber's requirements\n");
5321 if(publisher->audio && !subscriber->audio_offered)
5322 janus_sdp_mline_remove(offer, JANUS_SDP_AUDIO);
5323 if(publisher->video && !subscriber->video_offered)
5324 janus_sdp_mline_remove(offer, JANUS_SDP_VIDEO);
5325 if(publisher->data && !subscriber->data_offered)
5326 janus_sdp_mline_remove(offer, JANUS_SDP_APPLICATION);
5327 }
5328 char* sdp = janus_sdp_write(offer);
5329 json_t *jsep = json_pack("{ssss}", "type", "offer", "sdp", sdp);
5330 g_free(sdp);
5331 janus_mutex_unlock(&publisher->subscribers_mutex);
5332 /* How long will the Janus core take to push the event? */
5333 g_atomic_int_set(&session->hangingup, 0);
5334 gint64 start = janus_get_monotonic_time();
5335 int res = gateway->push_event(msg->handle, &janus_videoroom_plugin, msg->transaction, event, jsep);
5336 JANUS_LOG(LOG_VERB, " >> Pushing event: %d (took %"SCNu64" us)\n", res, janus_get_monotonic_time()-start);
5337 json_decref(event);
5338 json_decref(jsep);
5339 janus_videoroom_message_free(msg);
5340 /* Also notify event handlers */
5341 if(notify_events && gateway->events_is_enabled()) {
5342 json_t *info = json_object();
5343 json_object_set_new(info, "event", json_string("subscribing"));
5344 json_object_set_new(info, "room", json_integer(subscriber->room_id));
5345 json_object_set_new(info, "feed", json_integer(feed_id));
5346 json_object_set_new(info, "private_id", json_integer(pvt_id));
5347 gateway->notify_event(&janus_videoroom_plugin, session->handle, info);
5348 }
5349 continue;
5350 }
5351 janus_mutex_unlock(&publisher->subscribers_mutex);
5352 }
5353 } else {
5354 janus_mutex_unlock(&videoroom->mutex);
5355 JANUS_LOG(LOG_ERR, "Invalid element (ptype)\n");
5356 error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
5357 g_snprintf(error_cause, 512, "Invalid element (ptype)");
5358 goto error;
5359 }
5360 } else if(session->participant_type == janus_videoroom_p_type_publisher) {
5361 /* Handle this publisher */
5362 participant = janus_videoroom_session_get_publisher(session);
5363 if(participant == NULL) {
5364 JANUS_LOG(LOG_ERR, "Invalid participant instance\n");
5365 error_code = JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR;
5366 g_snprintf(error_cause, 512, "Invalid participant instance");
5367 goto error;
5368 }
5369 if(participant->room == NULL) {
5370 janus_refcount_decrease(&participant->ref);
5371 JANUS_LOG(LOG_ERR, "No such room\n");
5372 error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM;
5373 g_snprintf(error_cause, 512, "No such room");
5374 goto error;
5375 }
5376 if(!strcasecmp(request_text, "join") || !strcasecmp(request_text, "joinandconfigure")) {
5377 janus_refcount_decrease(&participant->ref);
5378 JANUS_LOG(LOG_ERR, "Already in as a publisher on this handle\n");
5379 error_code = JANUS_VIDEOROOM_ERROR_ALREADY_JOINED;
5380 g_snprintf(error_cause, 512, "Already in as a publisher on this handle");
5381 goto error;
5382 } else if(!strcasecmp(request_text, "configure") || !strcasecmp(request_text, "publish")) {
5383 if(!strcasecmp(request_text, "publish") && participant->sdp) {
5384 janus_refcount_decrease(&participant->ref);
5385 JANUS_LOG(LOG_ERR, "Can't publish, already published\n");
5386 error_code = JANUS_VIDEOROOM_ERROR_ALREADY_PUBLISHED;
5387 g_snprintf(error_cause, 512, "Can't publish, already published");
5388 goto error;
5389 }
5390 if(participant->kicked) {
5391 janus_refcount_decrease(&participant->ref);
5392 JANUS_LOG(LOG_ERR, "Unauthorized, you have been kicked\n");
5393 error_code = JANUS_VIDEOROOM_ERROR_UNAUTHORIZED;
5394 g_snprintf(error_cause, 512, "Unauthorized, you have been kicked");
5395 goto error;
5396 }
5397 /* Configure (or publish a new feed) audio/video/bitrate for this publisher */
5398 JANUS_VALIDATE_JSON_OBJECT(root, publish_parameters,
5399 error_code, error_cause, TRUE,
5400 JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT);
5401 if(error_code != 0) {
5402 janus_refcount_decrease(&participant->ref);
5403 goto error;
5404 }
5405 json_t *audio = json_object_get(root, "audio");
5406 json_t *audiocodec = json_object_get(root, "audiocodec");
5407 json_t *video = json_object_get(root, "video");
5408 json_t *videocodec = json_object_get(root, "videocodec");
5409 json_t *data = json_object_get(root, "data");
5410 json_t *bitrate = json_object_get(root, "bitrate");
5411 json_t *keyframe = json_object_get(root, "keyframe");
5412 json_t *record = json_object_get(root, "record");
5413 json_t *recfile = json_object_get(root, "filename");
5414 json_t *display = json_object_get(root, "display");
5415 json_t *update = json_object_get(root, "update");
5416 if(audio) {
5417 gboolean audio_active = json_is_true(audio);
5418 if(session->started && audio_active && !participant->audio_active) {
5419 /* Audio was just resumed, try resetting the RTP headers for viewers */
5420 janus_mutex_lock(&participant->subscribers_mutex);
5421 GSList *ps = participant->subscribers;
5422 while(ps) {
5423 janus_videoroom_subscriber *l = (janus_videoroom_subscriber *)ps->data;
5424 if(l)
5425 l->context.a_seq_reset = TRUE;
5426 ps = ps->next;
5427 }
5428 janus_mutex_unlock(&participant->subscribers_mutex);
5429 }
5430 participant->audio_active = audio_active;
5431 JANUS_LOG(LOG_VERB, "Setting audio property: %s (room %"SCNu64", user %"SCNu64")\n", participant->audio_active ? "true" : "false", participant->room_id, participant->user_id);
5432 }
5433 if(audiocodec && json_string_value(json_object_get(msg->jsep, "sdp")) != NULL) {
5434 /* The participant would like to use an audio codec in particular */
5435 janus_audiocodec acodec = janus_audiocodec_from_name(json_string_value(audiocodec));
5436 if(acodec == JANUS_AUDIOCODEC_NONE ||
5437 (acodec != participant->room->acodec[0] &&
5438 acodec != participant->room->acodec[1] &&
5439 acodec != participant->room->acodec[2])) {
5440 JANUS_LOG(LOG_ERR, "Participant asked for audio codec '%s', but it's not allowed (room %"SCNu64", user %"SCNu64")\n",
5441 json_string_value(audiocodec), participant->room_id, participant->user_id);
5442 janus_refcount_decrease(&participant->ref);
5443 error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
5444 g_snprintf(error_cause, 512, "Audio codec unavailable in this room");
5445 goto error;
5446 }
5447 participant->acodec = acodec;
5448 JANUS_LOG(LOG_VERB, "Participant asked for audio codec '%s' (room %"SCNu64", user %"SCNu64")\n",
5449 json_string_value(audiocodec), participant->room_id, participant->user_id);
5450 }
5451 if(video) {
5452 gboolean video_active = json_is_true(video);
5453 if(session->started && video_active && !participant->video_active) {
5454 /* Video was just resumed, try resetting the RTP headers for viewers */
5455 janus_mutex_lock(&participant->subscribers_mutex);
5456 GSList *ps = participant->subscribers;
5457 while(ps) {
5458 janus_videoroom_subscriber *l = (janus_videoroom_subscriber *)ps->data;
5459 if(l)
5460 l->context.v_seq_reset = TRUE;
5461 ps = ps->next;
5462 }
5463 janus_mutex_unlock(&participant->subscribers_mutex);
5464 }
5465 participant->video_active = video_active;
5466 JANUS_LOG(LOG_VERB, "Setting video property: %s (room %"SCNu64", user %"SCNu64")\n", participant->video_active ? "true" : "false", participant->room_id, participant->user_id);
5467 }
5468 if(videocodec && json_string_value(json_object_get(msg->jsep, "sdp")) != NULL) {
5469 /* The participant would like to use a video codec in particular */
5470 janus_videocodec vcodec = janus_videocodec_from_name(json_string_value(videocodec));
5471 if(vcodec == JANUS_VIDEOCODEC_NONE ||
5472 (vcodec != participant->room->vcodec[0] &&
5473 vcodec != participant->room->vcodec[1] &&
5474 vcodec != participant->room->vcodec[2])) {
5475 JANUS_LOG(LOG_ERR, "Participant asked for video codec '%s', but it's not allowed (room %"SCNu64", user %"SCNu64")\n",
5476 json_string_value(videocodec), participant->room_id, participant->user_id);
5477 janus_refcount_decrease(&participant->ref);
5478 error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
5479 g_snprintf(error_cause, 512, "Video codec unavailable in this room");
5480 goto error;
5481 }
5482 participant->vcodec = vcodec;
5483 JANUS_LOG(LOG_VERB, "Participant asked for video codec '%s' (room %"SCNu64", user %"SCNu64")\n",
5484 json_string_value(videocodec), participant->room_id, participant->user_id);
5485 }
5486 if(data) {
5487 gboolean data_active = json_is_true(data);
5488 participant->data_active = data_active;
5489 JANUS_LOG(LOG_VERB, "Setting data property: %s (room %"SCNu64", user %"SCNu64")\n", participant->data_active ? "true" : "false", participant->room_id, participant->user_id);
5490 }
5491 if(bitrate) {
5492 participant->bitrate = json_integer_value(bitrate);
5493 JANUS_LOG(LOG_VERB, "Setting video bitrate: %"SCNu32" (room %"SCNu64", user %"SCNu64")\n", participant->bitrate, participant->room_id, participant->user_id);
5494 /* Send a new REMB */
5495 if(session->started)
5496 participant->remb_latest = janus_get_monotonic_time();
5497 char rtcpbuf[24];
5498 janus_rtcp_remb((char *)(&rtcpbuf), 24, participant->bitrate);
5499 gateway->relay_rtcp(msg->handle, 1, rtcpbuf, 24);
5500 }
5501 if(keyframe && json_is_true(keyframe)) {
5502 /* Send a FIR */
5503 janus_videoroom_reqfir(participant, "Keyframe request");
5504 }
5505 janus_mutex_lock(&participant->rec_mutex);
5506 gboolean prev_recording_active = participant->recording_active;
5507 if(record) {
5508 participant->recording_active = json_is_true(record);
5509 JANUS_LOG(LOG_VERB, "Setting record property: %s (room %"SCNu64", user %"SCNu64")\n", participant->recording_active ? "true" : "false", participant->room_id, participant->user_id);
5510 }
5511 if(recfile) {
5512 participant->recording_base = g_strdup(json_string_value(recfile));
5513 JANUS_LOG(LOG_VERB, "Setting recording basename: %s (room %"SCNu64", user %"SCNu64")\n", participant->recording_base, participant->room_id, participant->user_id);
5514 }
5515 /* Do we need to do something with the recordings right now? */
5516 if(participant->recording_active != prev_recording_active) {
5517 /* Something changed */
5518 if(!participant->recording_active) {
5519 /* Not recording (anymore?) */
5520 janus_videoroom_recorder_close(participant);
5521 } else if(participant->recording_active && participant->sdp) {
5522 /* We've started recording, send a PLI/FIR and go on */
5523 janus_videoroom_recorder_create(
5524 participant, strstr(participant->sdp, "m=audio") != NULL,
5525 strstr(participant->sdp, "m=video") != NULL,
5526 strstr(participant->sdp, "m=application") != NULL);
5527 if(strstr(participant->sdp, "m=video")) {
5528 /* Send a FIR */
5529 janus_videoroom_reqfir(participant, "Recording video");
5530 }
5531 }
5532 }
5533 janus_mutex_unlock(&participant->rec_mutex);
5534 if(display) {
5535 janus_mutex_lock(&participant->room->mutex);
5536 char *old_display = participant->display;
5537 char *new_display = g_strdup(json_string_value(display));
5538 participant->display = new_display;
5539 g_free(old_display);
5540 json_t *display_event = json_object();
5541 json_object_set_new(display_event, "videoroom", json_string("event"));
5542 json_object_set_new(display_event, "id", json_integer(participant->user_id));
5543 json_object_set_new(display_event, "display", json_string(participant->display));
5544 if(participant->room && !participant->room->destroyed) {
5545 janus_videoroom_notify_participants(participant, display_event);
5546 }
5547 janus_mutex_unlock(&participant->room->mutex);
5548 json_decref(display_event);
5549 }
5550 /* A renegotiation may be taking place */
5551 gboolean do_update = update ? json_is_true(update) : FALSE;
5552 if(do_update && !sdp_update) {
5553 JANUS_LOG(LOG_WARN, "Got an 'update' request, but no SDP update? Ignoring...\n");
5554 }
5555 /* Done */
5556 event = json_object();
5557 json_object_set_new(event, "videoroom", json_string("event"));
5558 json_object_set_new(event, "room", json_integer(participant->room_id));
5559 json_object_set_new(event, "configured", json_string("ok"));
5560 /* Also notify event handlers */
5561 if(notify_events && gateway->events_is_enabled()) {
5562 json_t *info = json_object();
5563 json_object_set_new(info, "event", json_string("configured"));
5564 json_object_set_new(info, "room", json_integer(participant->room_id));
5565 json_object_set_new(info, "id", json_integer(participant->user_id));
5566 json_object_set_new(info, "audio_active", participant->audio_active ? json_true() : json_false());
5567 json_object_set_new(info, "video_active", participant->video_active ? json_true() : json_false());
5568 json_object_set_new(info, "data_active", participant->data_active ? json_true() : json_false());
5569 json_object_set_new(info, "bitrate", json_integer(participant->bitrate));
5570 if(participant->arc || participant->vrc || participant->drc) {
5571 json_t *recording = json_object();
5572 if(participant->arc && participant->arc->filename)
5573 json_object_set_new(recording, "audio", json_string(participant->arc->filename));
5574 if(participant->vrc && participant->vrc->filename)
5575 json_object_set_new(recording, "video", json_string(participant->vrc->filename));
5576 if(participant->drc && participant->drc->filename)
5577 json_object_set_new(recording, "data", json_string(participant->drc->filename));
5578 json_object_set_new(info, "recording", recording);
5579 }
5580 gateway->notify_event(&janus_videoroom_plugin, session->handle, info);
5581 }
5582 } else if(!strcasecmp(request_text, "unpublish")) {
5583 /* This participant wants to unpublish */
5584 if(!participant->sdp) {
5585 janus_refcount_decrease(&participant->ref);
5586 JANUS_LOG(LOG_ERR, "Can't unpublish, not published\n");
5587 error_code = JANUS_VIDEOROOM_ERROR_NOT_PUBLISHED;
5588 g_snprintf(error_cause, 512, "Can't unpublish, not published");
5589 goto error;
5590 }
5591 /* Tell the core to tear down the PeerConnection, hangup_media will do the rest */
5592 janus_videoroom_hangup_media(session->handle);
5593 gateway->close_pc(session->handle);
5594 /* Done */
5595 event = json_object();
5596 json_object_set_new(event, "videoroom", json_string("event"));
5597 json_object_set_new(event, "room", json_integer(participant->room_id));
5598 json_object_set_new(event, "unpublished", json_string("ok"));
5599 } else if(!strcasecmp(request_text, "leave")) {
5600 /* Prepare an event to confirm the request */
5601 event = json_object();
5602 json_object_set_new(event, "videoroom", json_string("event"));
5603 json_object_set_new(event, "room", json_integer(participant->room_id));
5604 json_object_set_new(event, "leaving", json_string("ok"));
5605 /* This publisher is leaving, tell everybody */
5606 janus_videoroom_leave_or_unpublish(participant, TRUE, FALSE);
5607 /* Done */
5608 participant->audio_active = FALSE;
5609 participant->video_active = FALSE;
5610 participant->data_active = FALSE;
5611 session->started = FALSE;
5612 //~ session->destroy = TRUE;
5613 } else {
5614 janus_refcount_decrease(&participant->ref);
5615 JANUS_LOG(LOG_ERR, "Unknown request '%s'\n", request_text);
5616 error_code = JANUS_VIDEOROOM_ERROR_INVALID_REQUEST;
5617 g_snprintf(error_cause, 512, "Unknown request '%s'", request_text);
5618 goto error;
5619 }
5620 janus_refcount_decrease(&participant->ref);
5621 } else if(session->participant_type == janus_videoroom_p_type_subscriber) {
5622 /* Handle this subscriber */
5623 janus_videoroom_subscriber *subscriber = (janus_videoroom_subscriber *)session->participant;
5624 if(subscriber == NULL) {
5625 JANUS_LOG(LOG_ERR, "Invalid subscriber instance\n");
5626 error_code = JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR;
5627 g_snprintf(error_cause, 512, "Invalid subscriber instance");
5628 goto error;
5629 }
5630 if(subscriber->room == NULL) {
5631 JANUS_LOG(LOG_ERR, "No such room\n");
5632 error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM;
5633 g_snprintf(error_cause, 512, "No such room");
5634 goto error;
5635 }
5636 if(!strcasecmp(request_text, "join")) {
5637 JANUS_LOG(LOG_ERR, "Already in as a subscriber on this handle\n");
5638 error_code = JANUS_VIDEOROOM_ERROR_ALREADY_JOINED;
5639 g_snprintf(error_cause, 512, "Already in as a subscriber on this handle");
5640 goto error;
5641 } else if(!strcasecmp(request_text, "start")) {
5642 /* Start/restart receiving the publisher streams */
5643 if(subscriber->paused && msg->jsep == NULL) {
5644 /* This is just resuming a paused stream, reset the RTP sequence numbers */
5645 subscriber->context.a_seq_reset = TRUE;
5646 subscriber->context.v_seq_reset = TRUE;
5647 }
5648 subscriber->paused = FALSE;
5649 event = json_object();
5650 json_object_set_new(event, "videoroom", json_string("event"));
5651 json_object_set_new(event, "room", json_integer(subscriber->room_id));
5652 json_object_set_new(event, "started", json_string("ok"));
5653 } else if(!strcasecmp(request_text, "configure")) {
5654 JANUS_VALIDATE_JSON_OBJECT(root, configure_parameters,
5655 error_code, error_cause, TRUE,
5656 JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT);
5657 if(error_code != 0)
5658 goto error;
5659 if(subscriber->kicked) {
5660 JANUS_LOG(LOG_ERR, "Unauthorized, you have been kicked\n");
5661 error_code = JANUS_VIDEOROOM_ERROR_UNAUTHORIZED;
5662 g_snprintf(error_cause, 512, "Unauthorized, you have been kicked");
5663 goto error;
5664 }
5665 json_t *audio = json_object_get(root, "audio");
5666 json_t *video = json_object_get(root, "video");
5667 json_t *data = json_object_get(root, "data");
5668 json_t *restart = json_object_get(root, "restart");
5669 json_t *update = json_object_get(root, "update");
5670 json_t *spatial = json_object_get(root, "spatial_layer");
5671 json_t *sc_substream = json_object_get(root, "substream");
5672 if(json_integer_value(spatial) < 0 || json_integer_value(spatial) > 2 ||
5673 json_integer_value(sc_substream) < 0 || json_integer_value(sc_substream) > 2) {
5674 JANUS_LOG(LOG_ERR, "Invalid element (substream/spatial_layer should be 0, 1 or 2)\n");
5675 error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
5676 g_snprintf(error_cause, 512, "Invalid value (substream/spatial_layer should be 0, 1 or 2)");
5677 goto error;
5678 }
5679 json_t *temporal = json_object_get(root, "temporal_layer");
5680 json_t *sc_temporal = json_object_get(root, "temporal");
5681 if(json_integer_value(temporal) < 0 || json_integer_value(temporal) > 2 ||
5682 json_integer_value(sc_temporal) < 0 || json_integer_value(sc_temporal) > 2) {
5683 JANUS_LOG(LOG_ERR, "Invalid element (temporal/temporal_layer should be 0, 1 or 2)\n");
5684 error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
5685 g_snprintf(error_cause, 512, "Invalid value (temporal/temporal_layer should be 0, 1 or 2)");
5686 goto error;
5687 }
5688 /* Update the audio/video/data flags, if set */
5689 janus_videoroom_publisher *publisher = subscriber->feed;
5690 if(publisher) {
5691 if(audio && publisher->audio && subscriber->audio_offered) {
5692 gboolean oldaudio = subscriber->audio;
5693 gboolean newaudio = json_is_true(audio);
5694 if(!oldaudio && newaudio) {
5695 /* Audio just resumed, reset the RTP sequence numbers */
5696 subscriber->context.a_seq_reset = TRUE;
5697 }
5698 subscriber->audio = newaudio;
5699 }
5700 if(video && publisher->video && subscriber->video_offered) {
5701 gboolean oldvideo = subscriber->video;
5702 gboolean newvideo = json_is_true(video);
5703 if(!oldvideo && newvideo) {
5704 /* Video just resumed, reset the RTP sequence numbers */
5705 subscriber->context.v_seq_reset = TRUE;
5706 }
5707 subscriber->video = newvideo;
5708 if(subscriber->video) {
5709 /* Send a FIR */
5710 janus_videoroom_reqfir(publisher, "Restoring video for subscriber");
5711 }
5712 }
5713 if(data && publisher->data && subscriber->data_offered)
5714 subscriber->data = json_is_true(data);
5715 /* Check if a simulcasting-related request is involved */
5716 if(sc_substream && (publisher->ssrc[0] != 0 || publisher->rid[0] != NULL)) {
5717 subscriber->sim_context.substream_target = json_integer_value(sc_substream);
5718 JANUS_LOG(LOG_VERB, "Setting video SSRC to let through (simulcast): %"SCNu32" (index %d, was %d)\n",
5719 publisher->ssrc[subscriber->sim_context.substream],
5720 subscriber->sim_context.substream_target,
5721 subscriber->sim_context.substream);
5722 if(subscriber->sim_context.substream_target == subscriber->sim_context.substream) {
5723 /* No need to do anything, we're already getting the right substream, so notify the user */
5724 json_t *event = json_object();
5725 json_object_set_new(event, "videoroom", json_string("event"));
5726 json_object_set_new(event, "room", json_integer(subscriber->room_id));
5727 json_object_set_new(event, "substream", json_integer(subscriber->sim_context.substream));
5728 gateway->push_event(msg->handle, &janus_videoroom_plugin, NULL, event, NULL);
5729 json_decref(event);
5730 } else {
5731 /* Send a FIR */
5732 janus_videoroom_reqfir(publisher, "Simulcasting substream change");
5733 }
5734 }
5735 if(subscriber->feed && subscriber->feed->vcodec == JANUS_VIDEOCODEC_VP8 &&
5736 sc_temporal && (publisher->ssrc[0] != 0 || publisher->rid[0] != NULL)) {
5737 subscriber->sim_context.templayer_target = json_integer_value(sc_temporal);
5738 JANUS_LOG(LOG_VERB, "Setting video temporal layer to let through (simulcast): %d (was %d)\n",
5739 subscriber->sim_context.templayer_target, subscriber->sim_context.templayer);
5740 if(subscriber->sim_context.templayer_target == subscriber->sim_context.templayer) {
5741 /* No need to do anything, we're already getting the right temporal, so notify the user */
5742 json_t *event = json_object();
5743 json_object_set_new(event, "videoroom", json_string("event"));
5744 json_object_set_new(event, "room", json_integer(subscriber->room_id));
5745 json_object_set_new(event, "temporal", json_integer(subscriber->sim_context.templayer));
5746 gateway->push_event(msg->handle, &janus_videoroom_plugin, NULL, event, NULL);
5747 json_decref(event);
5748 } else {
5749 /* Send a FIR */
5750 janus_videoroom_reqfir(publisher, "Simulcasting temporal layer change");
5751 }
5752 }
5753 }
5754 if(subscriber->room->do_svc) {
5755 /* Also check if the viewer is trying to configure a layer change */
5756 if(spatial) {
5757 int spatial_layer = json_integer_value(spatial);
5758 if(spatial_layer > 1) {
5759 JANUS_LOG(LOG_WARN, "Spatial layer higher than 1, it will be ignored if using EnabledByFlag_2SL3TL\n");
5760 }
5761 if(spatial_layer == subscriber->spatial_layer) {
5762 /* No need to do anything, we're already getting the right spatial layer, so notify the user */
5763 json_t *event = json_object();
5764 json_object_set_new(event, "videoroom", json_string("event"));
5765 json_object_set_new(event, "room", json_integer(subscriber->room_id));
5766 json_object_set_new(event, "spatial_layer", json_integer(subscriber->spatial_layer));
5767 gateway->push_event(msg->handle, &janus_videoroom_plugin, NULL, event, NULL);
5768 json_decref(event);
5769 } else if(spatial_layer != subscriber->target_spatial_layer) {
5770 /* Send a FIR to the new RTP forward publisher */
5771 janus_videoroom_reqfir(publisher, "Need to downscale spatially");
5772 }
5773 subscriber->target_spatial_layer = spatial_layer;
5774 }
5775 if(temporal) {
5776 int temporal_layer = json_integer_value(temporal);
5777 if(temporal_layer > 2) {
5778 JANUS_LOG(LOG_WARN, "Temporal layer higher than 2, will probably be ignored\n");
5779 }
5780 if(temporal_layer == subscriber->temporal_layer) {
5781 /* No need to do anything, we're already getting the right temporal layer, so notify the user */
5782 json_t *event = json_object();
5783 json_object_set_new(event, "videoroom", json_string("event"));
5784 json_object_set_new(event, "room", json_integer(subscriber->room_id));
5785 json_object_set_new(event, "temporal_layer", json_integer(subscriber->temporal_layer));
5786 gateway->push_event(msg->handle, &janus_videoroom_plugin, NULL, event, NULL);
5787 json_decref(event);
5788 }
5789 subscriber->target_temporal_layer = temporal_layer;
5790 }
5791 }
5792 event = json_object();
5793 json_object_set_new(event, "videoroom", json_string("event"));
5794 json_object_set_new(event, "room", json_integer(subscriber->room_id));
5795 json_object_set_new(event, "configured", json_string("ok"));
5796 /* The user may be interested in an ICE restart */
5797 gboolean do_restart = restart ? json_is_true(restart) : FALSE;
5798 gboolean do_update = update ? json_is_true(update) : FALSE;
5799 if(sdp_update || do_restart || do_update) {
5800 /* Negotiate by sending the selected publisher SDP back, and/or force an ICE restart */
5801 if(publisher->sdp != NULL) {
5802 char temp_error[512];
5803 JANUS_LOG(LOG_VERB, "Munging SDP offer (update) to adapt it to the subscriber's requirements\n");
5804 janus_sdp *offer = janus_sdp_parse(publisher->sdp, temp_error, sizeof(temp_error));
5805 if(publisher->audio && !subscriber->audio_offered)
5806 janus_sdp_mline_remove(offer, JANUS_SDP_AUDIO);
5807 if(publisher->video && !subscriber->video_offered)
5808 janus_sdp_mline_remove(offer, JANUS_SDP_VIDEO);
5809 if(publisher->data && !subscriber->data_offered)
5810 janus_sdp_mline_remove(offer, JANUS_SDP_APPLICATION);
5811 /* This is an update, check if we need to update */
5812 janus_sdp_mtype mtype[3] = { JANUS_SDP_AUDIO, JANUS_SDP_VIDEO, JANUS_SDP_APPLICATION };
5813 int i=0;
5814 for(i=0; i<3; i++) {
5815 janus_sdp_mline *m = janus_sdp_mline_find(subscriber->sdp, mtype[i]);
5816 janus_sdp_mline *m_new = janus_sdp_mline_find(offer, mtype[i]);
5817 if(m != NULL && m->port > 0 && m->direction != JANUS_SDP_INACTIVE) {
5818 /* We have such an m-line and it's active, should it be changed? */
5819 if(m_new == NULL || m_new->port == 0 || m_new->direction == JANUS_SDP_INACTIVE) {
5820 /* Turn the m-line to inactive */
5821 m->direction = JANUS_SDP_INACTIVE;
5822 }
5823 } else {
5824 /* We don't have such an m-line or it's disabled, should it be added/enabled? */
5825 if(m_new != NULL && m_new->port > 0 && m_new->direction != JANUS_SDP_INACTIVE) {
5826 if(m != NULL) {
5827 m->port = m_new->port;
5828 m->direction = m_new->direction;
5829 } else {
5830 /* Add the new m-line */
5831 m = janus_sdp_mline_create(m_new->type, m_new->port, m_new->proto, m_new->direction);
5832 subscriber->sdp->m_lines = g_list_append(subscriber->sdp->m_lines, m);
5833 }
5834 /* Copy/replace the other properties */
5835 m->c_ipv4 = m_new->c_ipv4;
5836 if(m_new->c_addr && (m->c_addr == NULL || strcmp(m->c_addr, m_new->c_addr))) {
5837 g_free(m->c_addr);
5838 m->c_addr = g_strdup(m_new->c_addr);
5839 }
5840 if(m_new->b_name && (m->b_name == NULL || strcmp(m->b_name, m_new->b_name))) {
5841 g_free(m->b_name);
5842 m->b_name = g_strdup(m_new->b_name);
5843 }
5844 m->b_value = m_new->b_value;
5845 g_list_free_full(m->fmts, (GDestroyNotify)g_free);
5846 m->fmts = NULL;
5847 GList *fmts = m_new->fmts;
5848 while(fmts) {
5849 char *fmt = (char *)fmts->data;
5850 if(fmt)
5851 m->fmts = g_list_append(m->fmts,g_strdup(fmt));
5852 fmts = fmts->next;
5853 }
5854 g_list_free(m->ptypes);
5855 m->ptypes = g_list_copy(m_new->ptypes);
5856 g_list_free_full(m->attributes, (GDestroyNotify)janus_sdp_attribute_destroy);
5857 m->attributes = NULL;
5858 GList *attr = m_new->attributes;
5859 while(attr) {
5860 janus_sdp_attribute *a = (janus_sdp_attribute *)attr->data;
5861 janus_sdp_attribute_add_to_mline(m,
5862 janus_sdp_attribute_create(a->name, "%s", a->value));
5863 attr = attr->next;
5864 }
5865 }
5866 }
5867 }
5868 janus_sdp_destroy(offer);
5869 session->sdp_version++;
5870 subscriber->sdp->o_version = session->sdp_version;
5871 char *newsdp = janus_sdp_write(subscriber->sdp);
5872 JANUS_LOG(LOG_VERB, "Updating subscriber:\n%s\n", newsdp);
5873 json_t *jsep = json_pack("{ssss}", "type", "offer", "sdp", newsdp);
5874 if(do_restart)
5875 json_object_set_new(jsep, "restart", json_true());
5876 /* How long will the Janus core take to push the event? */
5877 gint64 start = janus_get_monotonic_time();
5878 int res = gateway->push_event(msg->handle, &janus_videoroom_plugin, msg->transaction, event, jsep);
5879 JANUS_LOG(LOG_VERB, " >> Pushing event: %d (took %"SCNu64" us)\n", res, janus_get_monotonic_time()-start);
5880 json_decref(event);
5881 json_decref(jsep);
5882 g_free(newsdp);
5883 /* Any update in the media directions? */
5884 subscriber->audio = publisher->audio && subscriber->audio_offered;
5885 subscriber->video = publisher->video && subscriber->video_offered;
5886 subscriber->data = publisher->data && subscriber->data_offered;
5887 /* Done */
5888 janus_videoroom_message_free(msg);
5889 continue;
5890 }
5891 }
5892 } else if(!strcasecmp(request_text, "pause")) {
5893 /* Stop receiving the publisher streams for a while */
5894 subscriber->paused = TRUE;
5895 event = json_object();
5896 json_object_set_new(event, "videoroom", json_string("event"));
5897 json_object_set_new(event, "room", json_integer(subscriber->room_id));
5898 json_object_set_new(event, "paused", json_string("ok"));
5899 } else if(!strcasecmp(request_text, "switch")) {
5900 /* This subscriber wants to switch to a different publisher */
5901 JANUS_VALIDATE_JSON_OBJECT(root, subscriber_parameters,
5902 error_code, error_cause, TRUE,
5903 JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT);
5904 if(error_code != 0)
5905 goto error;
5906 json_t *feed = json_object_get(root, "feed");
5907 guint64 feed_id = json_integer_value(feed);
5908 json_t *audio = json_object_get(root, "audio");
5909 json_t *video = json_object_get(root, "video");
5910 json_t *data = json_object_get(root, "data");
5911 if(!subscriber->room) {
5912 JANUS_LOG(LOG_ERR, "Room Destroyed \n");
5913 error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM;
5914 g_snprintf(error_cause, 512, "No such room ");
5915 goto error;
5916 }
5917 if(g_atomic_int_get(&subscriber->destroyed)) {
5918 JANUS_LOG(LOG_ERR, "Room Destroyed (%"SCNu64")\n", subscriber->room_id);
5919 error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM;
5920 g_snprintf(error_cause, 512, "No such room (%"SCNu64")", subscriber->room_id);
5921 goto error;
5922 }
5923 janus_mutex_lock(&subscriber->room->mutex);
5924 janus_videoroom_publisher *publisher = g_hash_table_lookup(subscriber->room->participants, &feed_id);
5925 if(publisher == NULL || g_atomic_int_get(&publisher->destroyed) || publisher->sdp == NULL) {
5926 JANUS_LOG(LOG_ERR, "No such feed (%"SCNu64")\n", feed_id);
5927 error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_FEED;
5928 g_snprintf(error_cause, 512, "No such feed (%"SCNu64")", feed_id);
5929 janus_mutex_unlock(&subscriber->room->mutex);
5930 goto error;
5931 }
5932 janus_refcount_increase(&publisher->ref);
5933 janus_refcount_increase(&publisher->session->ref);
5934 janus_mutex_unlock(&subscriber->room->mutex);
5935 gboolean paused = subscriber->paused;
5936 subscriber->paused = TRUE;
5937 /* Unsubscribe from the previous publisher */
5938 janus_videoroom_publisher *prev_feed = subscriber->feed;
5939 if(prev_feed) {
5940 /* ... but make sure the codecs are compliant first */
5941 if(publisher->acodec != prev_feed->acodec || publisher->vcodec != prev_feed->vcodec) {
5942 janus_refcount_decrease(&publisher->session->ref);
5943 janus_refcount_decrease(&publisher->ref);
5944 subscriber->paused = paused;
5945 JANUS_LOG(LOG_ERR, "The two publishers are not using the same codecs, can't switch\n");
5946 error_code = JANUS_VIDEOROOM_ERROR_INVALID_SDP;
5947 g_snprintf(error_cause, 512, "The two publishers are not using the same codecs, can't switch");
5948 goto error;
5949 }
5950 /* Go on */
5951 janus_mutex_lock(&prev_feed->subscribers_mutex);
5952 prev_feed->subscribers = g_slist_remove(prev_feed->subscribers, subscriber);
5953 janus_mutex_unlock(&prev_feed->subscribers_mutex);
5954 janus_refcount_decrease(&prev_feed->session->ref);
5955 g_clear_pointer(&subscriber->feed, janus_videoroom_publisher_dereference);
5956 }
5957 /* Subscribe to the new one */
5958 subscriber->audio = audio ? json_is_true(audio) : TRUE; /* True by default */
5959 if(!publisher->audio)
5960 subscriber->audio = FALSE; /* ... unless the publisher isn't sending any audio */
5961 subscriber->video = video ? json_is_true(video) : TRUE; /* True by default */
5962 if(!publisher->video)
5963 subscriber->video = FALSE; /* ... unless the publisher isn't sending any video */
5964 subscriber->data = data ? json_is_true(data) : TRUE; /* True by default */
5965 if(!publisher->data)
5966 subscriber->data = FALSE; /* ... unless the publisher isn't sending any data */
5967 if(subscriber->room && subscriber->room->do_svc) {
5968 /* This subscriber belongs to a room where VP9 SVC has been enabled,
5969 * let's assume we're interested in all layers for the time being */
5970 subscriber->spatial_layer = -1;
5971 subscriber->target_spatial_layer = 2; /* FIXME Chrome sends 0, 1 and 2 (if using EnabledByFlag_3SL3TL) */
5972 subscriber->temporal_layer = -1;
5973 subscriber->target_temporal_layer = 2; /* FIXME Chrome sends 0, 1 and 2 */
5974 }
5975 janus_mutex_lock(&publisher->subscribers_mutex);
5976 publisher->subscribers = g_slist_append(publisher->subscribers, subscriber);
5977 janus_mutex_unlock(&publisher->subscribers_mutex);
5978 subscriber->feed = publisher;
5979 /* Send a FIR to the new publisher */
5980 janus_videoroom_reqfir(publisher, "Switching existing subscriber to new publisher");
5981 /* Done */
5982 subscriber->paused = paused;
5983 event = json_object();
5984 json_object_set_new(event, "videoroom", json_string("event"));
5985 json_object_set_new(event, "switched", json_string("ok"));
5986 json_object_set_new(event, "room", json_integer(subscriber->room_id));
5987 json_object_set_new(event, "id", json_integer(feed_id));
5988 if(publisher->display)
5989 json_object_set_new(event, "display", json_string(publisher->display));
5990 /* Also notify event handlers */
5991 if(notify_events && gateway->events_is_enabled()) {
5992 json_t *info = json_object();
5993 json_object_set_new(info, "event", json_string("switched"));
5994 json_object_set_new(info, "room", json_integer(publisher->room_id));
5995 json_object_set_new(info, "feed", json_integer(publisher->user_id));
5996 gateway->notify_event(&janus_videoroom_plugin, session->handle, info);
5997 }
5998 } else if(!strcasecmp(request_text, "leave")) {
5999 guint64 room_id = subscriber ? subscriber->room_id : 0;
6000 /* Tell the core to tear down the PeerConnection, hangup_media will do the rest */
6001 janus_videoroom_hangup_media(session->handle);
6002 gateway->close_pc(session->handle);
6003 /* Send an event back */
6004 event = json_object();
6005 json_object_set_new(event, "videoroom", json_string("event"));
6006 json_object_set_new(event, "room", json_integer(room_id));
6007 json_object_set_new(event, "left", json_string("ok"));
6008 session->started = FALSE;
6009 } else {
6010 JANUS_LOG(LOG_ERR, "Unknown request '%s'\n", request_text);
6011 error_code = JANUS_VIDEOROOM_ERROR_INVALID_REQUEST;
6012 g_snprintf(error_cause, 512, "Unknown request '%s'", request_text);
6013 goto error;
6014 }
6015 }
6016
6017 /* Prepare JSON event */
6018 JANUS_LOG(LOG_VERB, "Preparing JSON event as a reply\n");
6019 /* Any SDP or update to handle? */
6020 const char *msg_sdp_type = json_string_value(json_object_get(msg->jsep, "type"));
6021 const char *msg_sdp = json_string_value(json_object_get(msg->jsep, "sdp"));
6022 json_t *msg_simulcast = json_object_get(msg->jsep, "simulcast");
6023 if(!msg_sdp) {
6024 /* No SDP to send */
6025 int ret = gateway->push_event(msg->handle, &janus_videoroom_plugin, msg->transaction, event, NULL);
6026 JANUS_LOG(LOG_VERB, " >> %d (%s)\n", ret, janus_get_api_error(ret));
6027 json_decref(event);
6028 } else {
6029 /* Generate offer or answer */
6030 JANUS_LOG(LOG_VERB, "This is involving a negotiation (%s) as well:\n%s\n", msg_sdp_type, msg_sdp);
6031 if(sdp_update) {
6032 /* Renegotiation: make sure the user provided an offer, and send answer */
6033 JANUS_LOG(LOG_VERB, " -- Updating existing publisher\n");
6034 session->sdp_version++; /* This needs to be increased when it changes */
6035 } else {
6036 /* New PeerConnection */
6037 session->sdp_version = 1; /* This needs to be increased when it changes */
6038 session->sdp_sessid = janus_get_real_time();
6039 }
6040 const char *type = NULL;
6041 if(!strcasecmp(msg_sdp_type, "offer")) {
6042 /* We need to answer */
6043 type = "answer";
6044 } else if(!strcasecmp(msg_sdp_type, "answer")) {
6045 /* We got an answer (from a subscriber?), no need to negotiate */
6046 g_atomic_int_set(&session->hangingup, 0);
6047 int ret = gateway->push_event(msg->handle, &janus_videoroom_plugin, msg->transaction, event, NULL);
6048 JANUS_LOG(LOG_VERB, " >> %d (%s)\n", ret, janus_get_api_error(ret));
6049 json_decref(event);
6050 janus_videoroom_message_free(msg);
6051 continue;
6052 } else {
6053 /* TODO We don't support anything else right now... */
6054 JANUS_LOG(LOG_ERR, "Unknown SDP type '%s'\n", msg_sdp_type);
6055 error_code = JANUS_VIDEOROOM_ERROR_INVALID_SDP_TYPE;
6056 g_snprintf(error_cause, 512, "Unknown SDP type '%s'", msg_sdp_type);
6057 goto error;
6058 }
6059 if(session->participant_type != janus_videoroom_p_type_publisher) {
6060 /* We shouldn't be here, we always offer ourselves */
6061 JANUS_LOG(LOG_ERR, "Only publishers send offers\n");
6062 error_code = JANUS_VIDEOROOM_ERROR_INVALID_SDP_TYPE;
6063 g_snprintf(error_cause, 512, "Only publishers send offers");
6064 goto error;
6065 } else {
6066 /* This is a new publisher: is there room? */
6067 participant = janus_videoroom_session_get_publisher(session);
6068 janus_videoroom *videoroom = participant->room;
6069 int count = 0;
6070 GHashTableIter iter;
6071 gpointer value;
6072 if(!videoroom) {
6073 error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM;
6074 goto error;
6075 }
6076 if(g_atomic_int_get(&videoroom->destroyed)) {
6077 error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_ROOM;
6078 goto error;
6079 }
6080 janus_mutex_lock(&videoroom->mutex);
6081 g_hash_table_iter_init(&iter, videoroom->participants);
6082 while (!g_atomic_int_get(&videoroom->destroyed) && g_hash_table_iter_next(&iter, NULL, &value)) {
6083 janus_videoroom_publisher *p = value;
6084 if(p != participant && p->sdp)
6085 count++;
6086 }
6087 janus_mutex_unlock(&videoroom->mutex);
6088 if(count == videoroom->max_publishers) {
6089 participant->audio_active = FALSE;
6090 participant->video_active = FALSE;
6091 participant->data_active = FALSE;
6092 JANUS_LOG(LOG_ERR, "Maximum number of publishers (%d) already reached\n", videoroom->max_publishers);
6093 error_code = JANUS_VIDEOROOM_ERROR_PUBLISHERS_FULL;
6094 g_snprintf(error_cause, 512, "Maximum number of publishers (%d) already reached", videoroom->max_publishers);
6095 goto error;
6096 }
6097 /* Now prepare the SDP to give back */
6098 if(strstr(msg_sdp, "mozilla") || strstr(msg_sdp, "Mozilla")) {
6099 participant->firefox = TRUE;
6100 }
6101 /* Start by parsing the offer */
6102 char error_str[512];
6103 janus_sdp *offer = janus_sdp_parse(msg_sdp, error_str, sizeof(error_str));
6104 if(offer == NULL) {
6105 json_decref(event);
6106 JANUS_LOG(LOG_ERR, "Error parsing offer: %s\n", error_str);
6107 error_code = JANUS_VIDEOROOM_ERROR_INVALID_SDP;
6108 g_snprintf(error_cause, 512, "Error parsing offer: %s", error_str);
6109 goto error;
6110 }
6111 GList *temp = offer->m_lines;
6112 while(temp) {
6113 /* Which media are available? */
6114 janus_sdp_mline *m = (janus_sdp_mline *)temp->data;
6115 if(m->type == JANUS_SDP_AUDIO && m->port > 0 &&
6116 m->direction != JANUS_SDP_RECVONLY && m->direction != JANUS_SDP_INACTIVE) {
6117 participant->audio = TRUE;
6118 } else if(m->type == JANUS_SDP_VIDEO && m->port > 0 &&
6119 m->direction != JANUS_SDP_RECVONLY && m->direction != JANUS_SDP_INACTIVE) {
6120 participant->video = TRUE;
6121 } else if(m->type == JANUS_SDP_APPLICATION && m->port > 0) {
6122 participant->data = TRUE;
6123 }
6124 if(m->type == JANUS_SDP_AUDIO || m->type == JANUS_SDP_VIDEO) {
6125 /* Are the extmaps we care about there? */
6126 GList *ma = m->attributes;
6127 while(ma) {
6128 janus_sdp_attribute *a = (janus_sdp_attribute *)ma->data;
6129 if(a->value) {
6130 if(videoroom->audiolevel_ext && m->type == JANUS_SDP_AUDIO && strstr(a->value, JANUS_RTP_EXTMAP_AUDIO_LEVEL)) {
6131 participant->audio_level_extmap_id = atoi(a->value);
6132 } else if(videoroom->videoorient_ext && m->type == JANUS_SDP_VIDEO && strstr(a->value, JANUS_RTP_EXTMAP_VIDEO_ORIENTATION)) {
6133 participant->video_orient_extmap_id = atoi(a->value);
6134 } else if(videoroom->playoutdelay_ext && m->type == JANUS_SDP_VIDEO && strstr(a->value, JANUS_RTP_EXTMAP_PLAYOUT_DELAY)) {
6135 participant->playout_delay_extmap_id = atoi(a->value);
6136 } else if(m->type == JANUS_SDP_AUDIO && !strcasecmp(a->name, "fmtp") && strstr(a->value, "useinbandfec=1")) {
6137 participant->do_opusfec = videoroom->do_opusfec;
6138 }
6139 }
6140 ma = ma->next;
6141 }
6142 }
6143 temp = temp->next;
6144 }
6145 /* Prepare an answer now: force the room codecs and recvonly on the Janus side */
6146 JANUS_LOG(LOG_VERB, "The publisher %s going to send an audio stream\n", participant->audio ? "is" : "is NOT");
6147 JANUS_LOG(LOG_VERB, "The publisher %s going to send a video stream\n", participant->video ? "is" : "is NOT");
6148 JANUS_LOG(LOG_VERB, "The publisher %s going to open a data channel\n", participant->data ? "is" : "is NOT");
6149 /* Check the codecs we can use, or the ones we should */
6150 if(participant->acodec == JANUS_AUDIOCODEC_NONE) {
6151 int i=0;
6152 for(i=0; i<3; i++) {
6153 if(videoroom->acodec[i] == JANUS_AUDIOCODEC_NONE)
6154 continue;
6155 if(janus_sdp_get_codec_pt(offer, janus_audiocodec_name(videoroom->acodec[i])) != -1) {
6156 participant->acodec = videoroom->acodec[i];
6157 break;
6158 }
6159 }
6160 }
6161 JANUS_LOG(LOG_VERB, "The publisher is going to use the %s audio codec\n", janus_audiocodec_name(participant->acodec));
6162 participant->audio_pt = janus_audiocodec_pt(participant->acodec);
6163 if(participant->vcodec == JANUS_VIDEOCODEC_NONE) {
6164 int i=0;
6165 for(i=0; i<3; i++) {
6166 if(videoroom->vcodec[i] == JANUS_VIDEOCODEC_NONE)
6167 continue;
6168 if(janus_sdp_get_codec_pt(offer, janus_videocodec_name(videoroom->vcodec[i])) != -1) {
6169 participant->vcodec = videoroom->vcodec[i];
6170 break;
6171 }
6172 }
6173 }
6174 JANUS_LOG(LOG_VERB, "The publisher is going to use the %s video codec\n", janus_videocodec_name(participant->vcodec));
6175 participant->video_pt = janus_videocodec_pt(participant->vcodec);
6176 janus_sdp *answer = janus_sdp_generate_answer(offer,
6177 JANUS_SDP_OA_AUDIO_CODEC, janus_audiocodec_name(participant->acodec),
6178 JANUS_SDP_OA_AUDIO_DIRECTION, JANUS_SDP_RECVONLY,
6179 JANUS_SDP_OA_AUDIO_FMTP, participant->do_opusfec ? "useinbandfec=1" : NULL,
6180 JANUS_SDP_OA_VIDEO_CODEC, janus_videocodec_name(participant->vcodec),
6181 JANUS_SDP_OA_VIDEO_DIRECTION, JANUS_SDP_RECVONLY,
6182 JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_MID,
6183 JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_RID,
6184 JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_REPAIRED_RID,
6185 JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_FRAME_MARKING,
6186 JANUS_SDP_OA_ACCEPT_EXTMAP, videoroom->audiolevel_ext ? JANUS_RTP_EXTMAP_AUDIO_LEVEL : NULL,
6187 JANUS_SDP_OA_ACCEPT_EXTMAP, videoroom->videoorient_ext ? JANUS_RTP_EXTMAP_VIDEO_ORIENTATION : NULL,
6188 JANUS_SDP_OA_ACCEPT_EXTMAP, videoroom->playoutdelay_ext ? JANUS_RTP_EXTMAP_PLAYOUT_DELAY : NULL,
6189 JANUS_SDP_OA_ACCEPT_EXTMAP, videoroom->transport_wide_cc_ext ? JANUS_RTP_EXTMAP_TRANSPORT_WIDE_CC : NULL,
6190 JANUS_SDP_OA_DONE);
6191 janus_sdp_destroy(offer);
6192 /* Replace the session name */
6193 g_free(answer->s_name);
6194 char s_name[100];
6195 g_snprintf(s_name, sizeof(s_name), "VideoRoom %"SCNu64, videoroom->room_id);
6196 answer->s_name = g_strdup(s_name);
6197 /* Which media are REALLY available? (some may have been rejected) */
6198 participant->audio = FALSE;
6199 participant->video = FALSE;
6200 participant->data = FALSE;
6201 temp = answer->m_lines;
6202 while(temp) {
6203 janus_sdp_mline *m = (janus_sdp_mline *)temp->data;
6204 if(m->type == JANUS_SDP_AUDIO && m->port > 0 && m->direction != JANUS_SDP_INACTIVE) {
6205 participant->audio = TRUE;
6206 } else if(m->type == JANUS_SDP_VIDEO && m->port > 0 && m->direction != JANUS_SDP_INACTIVE) {
6207 participant->video = TRUE;
6208 } else if(m->type == JANUS_SDP_APPLICATION && m->port > 0) {
6209 participant->data = TRUE;
6210 }
6211 temp = temp->next;
6212 }
6213 JANUS_LOG(LOG_VERB, "Per the answer, the publisher %s going to send an audio stream\n", participant->audio ? "is" : "is NOT");
6214 JANUS_LOG(LOG_VERB, "Per the answer, the publisher %s going to send a video stream\n", participant->video ? "is" : "is NOT");
6215 JANUS_LOG(LOG_VERB, "Per the answer, the publisher %s going to open a data channel\n", participant->data ? "is" : "is NOT");
6216 /* Update the event with info on the codecs that we'll be handling */
6217 if(event) {
6218 if(participant->audio)
6219 json_object_set_new(event, "audio_codec", json_string(janus_audiocodec_name(participant->acodec)));
6220 if(participant->video)
6221 json_object_set_new(event, "video_codec", json_string(janus_videocodec_name(participant->vcodec)));
6222 }
6223 /* Also add a bandwidth SDP attribute if we're capping the bitrate in the room */
6224 janus_sdp_mline *m = janus_sdp_mline_find(answer, JANUS_SDP_VIDEO);
6225 if(m != NULL && videoroom->bitrate > 0 && videoroom->bitrate_cap) {
6226 if(participant->firefox) {
6227 /* Use TIAS (bps) instead of AS (kbps) for the b= attribute, as explained here:
6228 * https://github.com/meetecho/janus-gateway/issues/1277#issuecomment-397677746 */
6229 m->b_name = g_strdup("TIAS");
6230 m->b_value = videoroom->bitrate;
6231 } else {
6232 m->b_name = g_strdup("AS");
6233 m->b_value = videoroom->bitrate/1000;
6234 }
6235 }
6236 /* Generate an SDP string we can send back to the publisher */
6237 char *answer_sdp = janus_sdp_write(answer);
6238 /* Now turn the SDP into what we'll send subscribers, using the static payload types for making switching easier */
6239 offer = janus_sdp_generate_offer(s_name, answer->c_addr,
6240 JANUS_SDP_OA_AUDIO, participant->audio,
6241 JANUS_SDP_OA_AUDIO_CODEC, janus_audiocodec_name(participant->acodec),
6242 JANUS_SDP_OA_AUDIO_PT, janus_audiocodec_pt(participant->acodec),
6243 JANUS_SDP_OA_AUDIO_DIRECTION, JANUS_SDP_SENDONLY,
6244 JANUS_SDP_OA_AUDIO_FMTP, participant->do_opusfec ? "useinbandfec=1" : NULL,
6245 JANUS_SDP_OA_VIDEO, participant->video,
6246 JANUS_SDP_OA_VIDEO_CODEC, janus_videocodec_name(participant->vcodec),
6247 JANUS_SDP_OA_VIDEO_PT, janus_videocodec_pt(participant->vcodec),
6248 JANUS_SDP_OA_VIDEO_DIRECTION, JANUS_SDP_SENDONLY,
6249 JANUS_SDP_OA_DATA, participant->data,
6250 JANUS_SDP_OA_DONE);
6251 /* Add the extmap attributes, if needed */
6252 if(participant->audio_level_extmap_id > 0) {
6253 janus_sdp_mline *m = janus_sdp_mline_find(offer, JANUS_SDP_AUDIO);
6254 if(m != NULL) {
6255 janus_sdp_attribute *a = janus_sdp_attribute_create("extmap",
6256 "%d %s\r\n", participant->audio_level_extmap_id, JANUS_RTP_EXTMAP_AUDIO_LEVEL);
6257 janus_sdp_attribute_add_to_mline(m, a);
6258 }
6259 }
6260 if(participant->video_orient_extmap_id > 0) {
6261 janus_sdp_mline *m = janus_sdp_mline_find(offer, JANUS_SDP_VIDEO);
6262 if(m != NULL) {
6263 janus_sdp_attribute *a = janus_sdp_attribute_create("extmap",
6264 "%d %s\r\n", participant->video_orient_extmap_id, JANUS_RTP_EXTMAP_VIDEO_ORIENTATION);
6265 janus_sdp_attribute_add_to_mline(m, a);
6266 }
6267 }
6268 if(participant->playout_delay_extmap_id > 0) {
6269 janus_sdp_mline *m = janus_sdp_mline_find(offer, JANUS_SDP_VIDEO);
6270 if(m != NULL) {
6271 janus_sdp_attribute *a = janus_sdp_attribute_create("extmap",
6272 "%d %s\r\n", participant->playout_delay_extmap_id, JANUS_RTP_EXTMAP_PLAYOUT_DELAY);
6273 janus_sdp_attribute_add_to_mline(m, a);
6274 }
6275 }
6276 /* Is this room recorded, or are we recording this publisher already? */
6277 janus_mutex_lock(&participant->rec_mutex);
6278 if(videoroom->record || participant->recording_active) {
6279 janus_videoroom_recorder_create(participant, participant->audio, participant->video, participant->data);
6280 }
6281 janus_mutex_unlock(&participant->rec_mutex);
6282 /* Generate an SDP string we can offer subscribers later on */
6283 char *offer_sdp = janus_sdp_write(offer);
6284 if(!sdp_update) {
6285 /* Is simulcasting involved */
6286 if(msg_simulcast && (participant->vcodec == JANUS_VIDEOCODEC_VP8 ||
6287 participant->vcodec == JANUS_VIDEOCODEC_H264)) {
6288 JANUS_LOG(LOG_VERB, "Publisher is going to do simulcasting\n");
6289 janus_rtp_simulcasting_prepare(msg_simulcast,
6290 &participant->rid_extmap_id,
6291 &participant->framemarking_ext_id,
6292 participant->ssrc, participant->rid);
6293 } else {
6294 /* No simulcasting involved */
6295 int i=0;
6296 for(i=0; i<3; i++) {
6297 participant->ssrc[i] = 0;
6298 g_free(participant->rid[i]);
6299 participant->rid[i] = NULL;
6300 }
6301 }
6302 }
6303 janus_sdp_destroy(offer);
6304 janus_sdp_destroy(answer);
6305 /* Send the answer back to the publisher */
6306 JANUS_LOG(LOG_VERB, "Handling publisher: turned this into an '%s':\n%s\n", type, answer_sdp);
6307 json_t *jsep = json_pack("{ssss}", "type", type, "sdp", answer_sdp);
6308 g_free(answer_sdp);
6309 /* How long will the Janus core take to push the event? */
6310 g_atomic_int_set(&session->hangingup, 0);
6311 gint64 start = janus_get_monotonic_time();
6312 int res = gateway->push_event(msg->handle, &janus_videoroom_plugin, msg->transaction, event, jsep);
6313 JANUS_LOG(LOG_VERB, " >> Pushing event: %d (took %"SCNu64" us)\n", res, janus_get_monotonic_time()-start);
6314 /* Done */
6315 if(res != JANUS_OK) {
6316 /* TODO Failed to negotiate? We should remove this publisher */
6317 g_free(offer_sdp);
6318 } else {
6319 /* Store the participant's SDP for interested subscribers */
6320 g_free(participant->sdp);
6321 participant->sdp = offer_sdp;
6322 /* We'll wait for the setup_media event before actually telling subscribers */
6323 }
6324 /* Unless this is an update, in which case schedule a new offer for all viewers */
6325 if(sdp_update) {
6326 json_t *update = json_object();
6327 json_object_set_new(update, "request", json_string("configure"));
6328 json_object_set_new(update, "update", json_true());
6329 janus_mutex_lock(&participant->subscribers_mutex);
6330 GSList *s = participant->subscribers;
6331 while(s) {
6332 janus_videoroom_subscriber *subscriber = (janus_videoroom_subscriber *)s->data;
6333 if(subscriber && subscriber->session && subscriber->session->handle) {
6334 /* Enqueue the fake request: this will trigger a renegotiation */
6335 janus_videoroom_message *msg = g_malloc(sizeof(janus_videoroom_message));
6336 janus_refcount_increase(&subscriber->session->ref);
6337 msg->handle = subscriber->session->handle;
6338 msg->message = update;
6339 msg->transaction = NULL;
6340 msg->jsep = NULL;
6341 json_incref(update);
6342 g_async_queue_push(messages, msg);
6343 }
6344 s = s->next;
6345 }
6346 janus_mutex_unlock(&participant->subscribers_mutex);
6347 json_decref(update);
6348 }
6349 json_decref(event);
6350 json_decref(jsep);
6351 }
6352 if(participant != NULL)
6353 janus_refcount_decrease(&participant->ref);
6354 }
6355 janus_videoroom_message_free(msg);
6356
6357 continue;
6358
6359error:
6360 {
6361 /* Prepare JSON error event */
6362 json_t *event = json_object();
6363 json_object_set_new(event, "videoroom", json_string("event"));
6364 json_object_set_new(event, "error_code", json_integer(error_code));
6365 json_object_set_new(event, "error", json_string(error_cause));
6366 int ret = gateway->push_event(msg->handle, &janus_videoroom_plugin, msg->transaction, event, NULL);
6367 JANUS_LOG(LOG_VERB, " >> Pushing event: %d (%s)\n", ret, janus_get_api_error(ret));
6368 json_decref(event);
6369 janus_videoroom_message_free(msg);
6370 }
6371 }
6372 JANUS_LOG(LOG_VERB, "Leaving VideoRoom handler thread\n");
6373 return NULL;
6374}
6375
6376/* Helper to quickly relay RTP packets from publishers to subscribers */
6377static void janus_videoroom_relay_rtp_packet(gpointer data, gpointer user_data) {
6378 janus_videoroom_rtp_relay_packet *packet = (janus_videoroom_rtp_relay_packet *)user_data;
6379 if(!packet || !packet->data || packet->length < 1) {
6380 JANUS_LOG(LOG_ERR, "Invalid packet...\n");
6381 return;
6382 }
6383 janus_videoroom_subscriber *subscriber = (janus_videoroom_subscriber *)data;
6384 if(!subscriber || !subscriber->session) {
6385 // JANUS_LOG(LOG_ERR, "Invalid session...\n");
6386 return;
6387 }
6388 if(subscriber->paused || subscriber->kicked) {
6389 // JANUS_LOG(LOG_ERR, "This subscriber paused the stream...\n");
6390 return;
6391 }
6392 janus_videoroom_session *session = subscriber->session;
6393 if(!session || !session->handle) {
6394 // JANUS_LOG(LOG_ERR, "Invalid session...\n");
6395 return;
6396 }
6397 if(!session->started) {
6398 // JANUS_LOG(LOG_ERR, "Streaming not started yet for this session...\n");
6399 return;
6400 }
6401
6402 /* Make sure there hasn't been a publisher switch by checking the SSRC */
6403 if(packet->is_video) {
6404 /* Check if this subscriber is subscribed to this medium */
6405 if(!subscriber->video) {
6406 /* Nope, don't relay */
6407 return;
6408 }
6409 /* Check if there's any SVC info to take into account */
6410 if(packet->svc) {
6411 /* There is: check if this is a layer that can be dropped for this viewer
6412 * Note: Following core inspired by the excellent job done by Sergio Garcia Murillo here:
6413 * https://github.com/medooze/media-server/blob/master/src/vp9/VP9LayerSelector.cpp */
6414 gboolean override_mark_bit = FALSE, has_marker_bit = packet->data->markerbit;
6415 int temporal_layer = subscriber->temporal_layer;
6416 if(subscriber->target_temporal_layer > subscriber->temporal_layer) {
6417 /* We need to upscale */
6418 JANUS_LOG(LOG_HUGE, "We need to upscale temporally:\n");
6419 if(packet->ubit && packet->bbit && packet->temporal_layer <= subscriber->target_temporal_layer) {
6420 JANUS_LOG(LOG_HUGE, " -- Upscaling temporal layer: %u --> %u\n",
6421 packet->temporal_layer, subscriber->target_temporal_layer);
6422 subscriber->temporal_layer = packet->temporal_layer;
6423 temporal_layer = subscriber->temporal_layer;
6424 /* Notify the viewer */
6425 json_t *event = json_object();
6426 json_object_set_new(event, "videoroom", json_string("event"));
6427 json_object_set_new(event, "room", json_integer(subscriber->room_id));
6428 json_object_set_new(event, "temporal_layer", json_integer(subscriber->temporal_layer));
6429 gateway->push_event(subscriber->session->handle, &janus_videoroom_plugin, NULL, event, NULL);
6430 json_decref(event);
6431 }
6432 } else if(subscriber->target_temporal_layer < subscriber->temporal_layer) {
6433 /* We need to downscale */
6434 JANUS_LOG(LOG_HUGE, "We need to downscale temporally:\n");
6435 if(packet->ebit) {
6436 JANUS_LOG(LOG_HUGE, " -- Downscaling temporal layer: %u --> %u\n",
6437 subscriber->temporal_layer, subscriber->target_temporal_layer);
6438 subscriber->temporal_layer = subscriber->target_temporal_layer;
6439 /* Notify the viewer */
6440 json_t *event = json_object();
6441 json_object_set_new(event, "videoroom", json_string("event"));
6442 json_object_set_new(event, "room", json_integer(subscriber->room_id));
6443 json_object_set_new(event, "temporal_layer", json_integer(subscriber->temporal_layer));
6444 gateway->push_event(subscriber->session->handle, &janus_videoroom_plugin, NULL, event, NULL);
6445 json_decref(event);
6446 }
6447 }
6448 if(temporal_layer < packet->temporal_layer) {
6449 /* Drop the packet: update the context to make sure sequence number is increased normally later */
6450 JANUS_LOG(LOG_HUGE, "Dropping packet (temporal layer %d < %d)\n", temporal_layer, packet->temporal_layer);
6451 subscriber->context.v_base_seq++;
6452 return;
6453 }
6454 int spatial_layer = subscriber->spatial_layer;
6455 if(subscriber->target_spatial_layer > subscriber->spatial_layer) {
6456 JANUS_LOG(LOG_HUGE, "We need to upscale spatially:\n");
6457 /* We need to upscale */
6458 if(packet->pbit == 0 && packet->bbit && packet->spatial_layer == subscriber->spatial_layer+1) {
6459 JANUS_LOG(LOG_HUGE, " -- Upscaling spatial layer: %u --> %u\n",
6460 packet->spatial_layer, subscriber->target_spatial_layer);
6461 subscriber->spatial_layer = packet->spatial_layer;
6462 spatial_layer = subscriber->spatial_layer;
6463 /* Notify the viewer */
6464 json_t *event = json_object();
6465 json_object_set_new(event, "videoroom", json_string("event"));
6466 json_object_set_new(event, "room", json_integer(subscriber->room_id));
6467 json_object_set_new(event, "spatial_layer", json_integer(subscriber->spatial_layer));
6468 gateway->push_event(subscriber->session->handle, &janus_videoroom_plugin, NULL, event, NULL);
6469 json_decref(event);
6470 }
6471 } else if(subscriber->target_spatial_layer < subscriber->spatial_layer) {
6472 /* We need to downscale */
6473 JANUS_LOG(LOG_HUGE, "We need to downscale spatially:\n");
6474 if(packet->ebit) {
6475 JANUS_LOG(LOG_HUGE, " -- Downscaling spatial layer: %u --> %u\n",
6476 subscriber->spatial_layer, subscriber->target_spatial_layer);
6477 subscriber->spatial_layer = subscriber->target_spatial_layer;
6478 /* Notify the viewer */
6479 json_t *event = json_object();
6480 json_object_set_new(event, "videoroom", json_string("event"));
6481 json_object_set_new(event, "room", json_integer(subscriber->room_id));
6482 json_object_set_new(event, "spatial_layer", json_integer(subscriber->spatial_layer));
6483 gateway->push_event(subscriber->session->handle, &janus_videoroom_plugin, NULL, event, NULL);
6484 json_decref(event);
6485 }
6486 }
6487 if(spatial_layer < packet->spatial_layer) {
6488 /* Drop the packet: update the context to make sure sequence number is increased normally later */
6489 JANUS_LOG(LOG_HUGE, "Dropping packet (spatial layer %d < %d)\n", spatial_layer, packet->spatial_layer);
6490 subscriber->context.v_base_seq++;
6491 return;
6492 } else if(packet->ebit && spatial_layer == packet->spatial_layer) {
6493 /* If we stop at layer 0, we need a marker bit now, as the one from layer 1 will not be received */
6494 override_mark_bit = TRUE;
6495 }
6496 /* If we got here, we can send the frame: this doesn't necessarily mean it's
6497 * one of the layers the user wants, as there may be dependencies involved */
6498 JANUS_LOG(LOG_HUGE, "Sending packet (spatial=%d, temporal=%d)\n",
6499 packet->spatial_layer, packet->temporal_layer);
6500 /* Fix sequence number and timestamp (publisher switching may be involved) */
6501 janus_rtp_header_update(packet->data, &subscriber->context, TRUE, 4500);
6502 if(override_mark_bit && !has_marker_bit) {
6503 packet->data->markerbit = 1;
6504 }
6505 if(gateway != NULL)
6506 gateway->relay_rtp(session->handle, packet->is_video, (char *)packet->data, packet->length);
6507 if(override_mark_bit && !has_marker_bit) {
6508 packet->data->markerbit = 0;
6509 }
6510 /* Restore the timestamp and sequence number to what the publisher set them to */
6511 packet->data->timestamp = htonl(packet->timestamp);
6512 packet->data->seq_number = htons(packet->seq_number);
6513 } else if(packet->ssrc[0] != 0) {
6514 /* Handle simulcast: make sure we have a payload to work with */
6515 int plen = 0;
6516 char *payload = janus_rtp_payload((char *)packet->data, packet->length, &plen);
6517 if(payload == NULL)
6518 return;
6519 /* Process this packet: don't relay if it's not the SSRC/layer we wanted to handle */
6520 gboolean relay = janus_rtp_simulcasting_context_process_rtp(&subscriber->sim_context,
6521 (char *)packet->data, packet->length, packet->ssrc, NULL, subscriber->feed->vcodec, &subscriber->context);
6522 /* Do we need to drop this? */
6523 if(!relay)
6524 return;
6525 /* Any event we should notify? */
6526 if(subscriber->sim_context.changed_substream) {
6527 /* Notify the user about the substream change */
6528 json_t *event = json_object();
6529 json_object_set_new(event, "videoroom", json_string("event"));
6530 json_object_set_new(event, "room", json_integer(subscriber->room_id));
6531 json_object_set_new(event, "substream", json_integer(subscriber->sim_context.substream));
6532 gateway->push_event(subscriber->session->handle, &janus_videoroom_plugin, NULL, event, NULL);
6533 json_decref(event);
6534 }
6535 if(subscriber->sim_context.need_pli && subscriber->feed && subscriber->feed->session &&
6536 subscriber->feed->session->handle) {
6537 /* Send a PLI */
6538 JANUS_LOG(LOG_VERB, "We need a PLI for the simulcast context\n");
6539 char rtcpbuf[12];
6540 memset(rtcpbuf, 0, 12);
6541 janus_rtcp_pli((char *)&rtcpbuf, 12);
6542 gateway->relay_rtcp(subscriber->feed->session->handle, 1, rtcpbuf, 12);
6543 }
6544 if(subscriber->sim_context.changed_temporal) {
6545 /* Notify the user about the temporal layer change */
6546 json_t *event = json_object();
6547 json_object_set_new(event, "videoroom", json_string("event"));
6548 json_object_set_new(event, "room", json_integer(subscriber->room_id));
6549 json_object_set_new(event, "temporal", json_integer(subscriber->sim_context.templayer));
6550 gateway->push_event(subscriber->session->handle, &janus_videoroom_plugin, NULL, event, NULL);
6551 json_decref(event);
6552 }
6553 /* If we got here, update the RTP header and send the packet */
6554 janus_rtp_header_update(packet->data, &subscriber->context, TRUE, 4500);
6555 char vp8pd[6];
6556 if(subscriber->feed && subscriber->feed->vcodec == JANUS_VIDEOCODEC_VP8) {
6557 /* For VP8, we save the original payload descriptor, to restore it after */
6558 memcpy(vp8pd, payload, sizeof(vp8pd));
6559 janus_vp8_simulcast_descriptor_update(payload, plen, &subscriber->vp8_context,
6560 subscriber->sim_context.changed_substream);
6561 }
6562 /* Send the packet */
6563 if(gateway != NULL)
6564 gateway->relay_rtp(session->handle, packet->is_video, (char *)packet->data, packet->length);
6565 /* Restore the timestamp and sequence number to what the publisher set them to */
6566 packet->data->timestamp = htonl(packet->timestamp);
6567 packet->data->seq_number = htons(packet->seq_number);
6568 if(subscriber->feed && subscriber->feed->vcodec == JANUS_VIDEOCODEC_VP8) {
6569 /* Restore the original payload descriptor as well, as it will be needed by the next viewer */
6570 memcpy(payload, vp8pd, sizeof(vp8pd));
6571 }
6572 } else {
6573 /* Fix sequence number and timestamp (publisher switching may be involved) */
6574 janus_rtp_header_update(packet->data, &subscriber->context, TRUE, 4500);
6575 /* Send the packet */
6576 if(gateway != NULL)
6577 gateway->relay_rtp(session->handle, packet->is_video, (char *)packet->data, packet->length);
6578 /* Restore the timestamp and sequence number to what the publisher set them to */
6579 packet->data->timestamp = htonl(packet->timestamp);
6580 packet->data->seq_number = htons(packet->seq_number);
6581 }
6582 } else {
6583 /* Check if this subscriber is subscribed to this medium */
6584 if(!subscriber->audio) {
6585 /* Nope, don't relay */
6586 return;
6587 }
6588 /* Fix sequence number and timestamp (publisher switching may be involved) */
6589 janus_rtp_header_update(packet->data, &subscriber->context, FALSE, 960);
6590 /* Send the packet */
6591 if(gateway != NULL)
6592 gateway->relay_rtp(session->handle, packet->is_video, (char *)packet->data, packet->length);
6593 /* Restore the timestamp and sequence number to what the publisher set them to */
6594 packet->data->timestamp = htonl(packet->timestamp);
6595 packet->data->seq_number = htons(packet->seq_number);
6596 }
6597
6598 return;
6599}
6600
6601static void janus_videoroom_relay_data_packet(gpointer data, gpointer user_data) {
6602 char *text = (char *)user_data;
6603 janus_videoroom_subscriber *subscriber = (janus_videoroom_subscriber *)data;
6604 if(!subscriber || !subscriber->session || !subscriber->data || subscriber->paused) {
6605 return;
6606 }
6607 janus_videoroom_session *session = subscriber->session;
6608 if(!session || !session->handle) {
6609 return;
6610 }
6611 if(!session->started) {
6612 return;
6613 }
6614 if(gateway != NULL && text != NULL) {
6615 JANUS_LOG(LOG_VERB, "Forwarding DataChannel message (%zu bytes) to viewer: %s\n", strlen(text), text);
6616 gateway->relay_data(session->handle, NULL, text, strlen(text));
6617 }
6618 return;
6619}
6620
6621/* The following methods are only relevant if RTCP is used for RTP forwarders */
6622static void janus_videoroom_rtp_forwarder_rtcp_receive(janus_videoroom_rtp_forwarder *forward) {
6623 char buffer[1500];
6624 struct sockaddr_storage remote_addr;
6625 socklen_t addrlen = sizeof(remote_addr);
6626 int len = recvfrom(forward->rtcp_fd, buffer, sizeof(buffer), 0, (struct sockaddr *)&remote_addr, &addrlen);
6627 if(len > 0 && janus_is_rtcp(buffer, len)) {
6628 JANUS_LOG(LOG_HUGE, "Got %s RTCP packet: %d bytes\n", forward->is_video ? "video" : "audio", len);
6629 /* We only handle incoming video PLIs or FIR at the moment */
6630 if(!janus_rtcp_has_fir(buffer, len) && !janus_rtcp_has_pli(buffer, len))
6631 return;
6632 janus_videoroom_reqfir((janus_videoroom_publisher *)forward->source, "RTCP from forwarder");
6633 }
6634}
6635
6636static void *janus_videoroom_rtp_forwarder_rtcp_thread(void *data) {
6637 JANUS_LOG(LOG_VERB, "Joining RTCP thread for RTP forwarders...\n");
6638 /* Run the main loop */
6639 g_main_loop_run(rtcpfwd_loop);
6640 /* When the loop ends, we're done */
6641 JANUS_LOG(LOG_VERB, "Leaving RTCP thread for RTP forwarders...\n");
6642 return NULL;
6643}