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