From 5ab4ae621960b338da8a836d59328df789509de6 Mon Sep 17 00:00:00 2001 From: Bogdanov Kirill Date: Wed, 10 Mar 2021 11:34:40 +0500 Subject: [PATCH] Squashed commit of the following: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit caaba91081ba8e5578a24bca1495a8572f08e65c Author: Tijmen de Mes Date: Tue Feb 23 14:57:17 2021 +0100 Added Content type to SIP message (#2567) * Added 'content_type' to received SIP MESSAGE * Added optional content type in sending SIP MESSAGE commit c9baba961165821f6c413a853bf3c1c4bb389f22 Author: Alessandro Toppi Date: Tue Feb 23 11:46:50 2021 +0100 clang/ubsan fixes (#2556) * Fix some clang warnings. * Fix UBSanitizer error when sending RTCP SR. commit beb28be6575a3c110d10b6b5b5b15de193299183 Author: Tvildo Date: Mon Feb 22 09:46:25 2021 -0800 add call_id in received sip message (#2563) Add call_id in received SIP MESSAGE and INFO commit 8246452617ea5b2c6b73e6bdea9ec9450e757719 Author: Lorenzo Miniero Date: Mon Feb 22 11:51:36 2021 +0100 Fixed missing mutexes around VideoRoom ACL management commit 4f8943adf7c3292a1a4aaa08fd022c1308b9357d Author: Tristan Matthews Date: Wed Feb 17 09:32:57 2021 -0500 ice: fix conncheck typo (#2560) No functional change since the typo was used consistently. commit 27dc51aa0094a0b41ec8b4f99964dfcf610dbcf6 Author: nicolasduteil Date: Wed Feb 17 15:27:45 2021 +0100 feat: add "call_id" to "calling", "declining", "updatingcall" & "incomingcall" events (#2557) commit 2c81d0277f664086610d12dff4c097dc0be04acf Author: Hritik Utekar Date: Wed Feb 17 19:54:46 2021 +0530 Video moderation always returns unmuted (#2559) commit 6503f42de8b64ac0fa2c7c9adb1260b1b1d2fa82 Author: Lorenzo Miniero Date: Wed Feb 17 13:53:53 2021 +0100 Fixed typo in keepalive-conncheck usage commit 1f45e02d5d5bb64fd163ac2d9cd7dedfd03b1bd4 Author: Alessandro Toppi Date: Mon Feb 15 16:38:22 2021 +0100 Set specific versions for Python 3 and meson in janus-ci yml. commit d7c9ef09da1a7ebca7c5a5bbb46ee3e6e1e42aa1 Author: Lorenzo Miniero Date: Mon Feb 15 15:28:48 2021 +0100 Added audiocodec/videocodec supporto to 'joinandconfigure' in VideoRoom API commit ad54495df09e8b96386df40b96b4212fe36a92b7 Author: Lorenzo Miniero Date: Fri Feb 12 15:28:21 2021 +0100 Add new option to configure ICE nomination mode, if libnice is recent enough (#2541) * Add new option to configure ICE nomination mode, if libnice is recent enough * Added support for libnice keepalive-conncheck property commit af8cc6e9f8b94b19d161f35edf339a67c107249f Author: Nadin Zajimovic Date: Fri Feb 12 09:40:36 2021 +0100 if inviting on destroy, send BYE instead of 480 response (#2554) commit ad8bf7925c8efe02635087296c2b841a8942889c Author: Alessandro Amirante Date: Thu Feb 11 17:49:25 2021 +0100 Fix typo in videoroom docs. commit 26f59581c22d651eb8aa7e51215c64a3f897e04a Author: Lorenzo Miniero Date: Wed Feb 10 16:51:19 2021 +0100 Fixed small leak in VideoRoom commit 8ab7a004009831fc4d0e8b2ef84155030bdfbb2e Author: Alessandro Toppi Date: Tue Feb 9 16:51:37 2021 +0100 Initialize packet.is_rtp to false. commit 66cf343cefbc4954f330ef4a251330ffd233bae6 Author: Lorenzo Miniero Date: Tue Feb 9 16:05:37 2021 +0100 Add resolution and bitrate to Record&Play playback commit 119d220c2e6324dd2233d8f93868fcaa1ae229fd Author: Aleksander Guryanov Date: Tue Feb 9 20:33:23 2021 +0700 Update janus.d.ts (#2553) Function getBitrate() actually returns a string commit 41399db99f0ac6eac5c19dff9080b9336ae60169 Author: Lorenzo Miniero Date: Mon Feb 8 16:27:14 2021 +0100 Allow up to 5 (rather than 3) audio/video codecs in the same VideoRoom commit b81dd6d1ed11363f78265f727b8892c99e765e84 Author: Lorenzo Miniero Date: Mon Feb 8 16:26:19 2021 +0100 Allow forcing audio/video codec for VideoRoom publishers via query string commit 576abf52fac85691c85c9a671d465f82ce5311ba Author: Lorenzo Miniero Date: Mon Feb 8 15:17:56 2021 +0100 Initialize VideoRoom participant recording state when room recording is active (fixes #2550) commit 0ba74fb4104beb65747a681302082897a9314186 Author: Lorenzo Miniero Date: Mon Feb 8 11:53:42 2021 +0100 Fixed broken AV1 post-processing commit 09daec4b7adf5a9dd03e59d21b1160392771043f Author: Lorenzo Miniero Date: Mon Feb 8 10:46:06 2021 +0100 Renamed extern janus_callbacks variables in Lua and Duktape plugins (#2540) commit 664022be731b902511de3e16b40e2bf893d7c732 Author: Lorenzo Miniero Date: Mon Feb 8 10:41:38 2021 +0100 Bumped to version 0.11.1 commit 7732127f71521d1f1b305fdcf1073b3602f15b6e Author: Lorenzo Miniero Date: Mon Feb 8 10:37:41 2021 +0100 Updated Changelog (0.10.10) commit 24a0eec87ba52b35824862ca1150904f48454f39 Author: Lorenzo Miniero Date: Thu Feb 4 11:53:36 2021 +0100 Videoroom race condition fixes (see #2509) (#2539) * Fixed missing room references that could cause crashes during race conditions * Fixed rare race condition on publisher join commit 62440c5e3faa902e081000bfccab22e9b078b5fc Author: Lorenzo Miniero Date: Thu Feb 4 11:52:44 2021 +0100 Fix parsing of SDP to find payload type matching profiles (fixes #2544) (#2549) commit 794e89ab896d8b87d26c25b6089edec756230f24 Author: Bender Date: Wed Feb 3 20:12:02 2021 +0300 janus.js (#2548) customizeSdp callback added to handleRemoteJsep to be able to mangle remote SDP if needed commit 213b6c7c63a2fca5450d3dd7672a9472e8235ed1 Author: Alessandro Toppi Date: Fri Jan 29 12:13:44 2021 +0100 Make compiler fail if implicit-function-declaration is encountered. commit dfa801656ee920598b2c97f18bb5bc2def185ee9 Author: Lorenzo Miniero Date: Fri Jan 29 10:21:50 2021 +0100 Fixed non-portable call to strlcpy, and comment styles, in RabbitMQ code (see #2430) commit b7b1e9e38021bf0bf2ebea3dad4d81a5b8e7e849 Merge: 19ecf483 c0f0e1e1 Author: Alessandro Toppi Date: Fri Jan 29 08:02:18 2021 +0100 Merge pull request #2430 from vgrid/master Updates RabbitMQ logic commit 19ecf48387e186db32f01f8aa41584f121010570 Author: Lorenzo Miniero Date: Thu Jan 28 11:54:55 2021 +0100 Fixed VideoRoom docs on ICE Restarts for subscribers (fixes #2537) commit 24548023f58c8dc6b31f122ddfb72c04ba5e919d Author: Lorenzo Miniero Date: Wed Jan 27 11:22:13 2021 +0100 Allow marking of RTP extensions in MJR recordings (#2527) commit 0bb49bc0f516dd79cfe6e2f7a7a5fcd4c8ad7e50 Author: Lorenzo Miniero Date: Wed Jan 27 11:21:09 2021 +0100 Moderator based muting/unmuting of VideoRoom streams (#2513) commit 5e685e3f24de9cc9f2ba57c026ebf5b1365b6f10 Author: Lorenzo Miniero Date: Wed Jan 27 11:19:35 2021 +0100 Reject a=extmap-allow-mixed in SDP, when offered commit c0f0e1e105de220e40ba08f6b3c1a0d52bc16307 Author: Chris Wiggins Date: Wed Jan 27 09:59:02 2021 +1300 Fix code style comments, also enable routing for direct exchanges commit 257eb802170417b734a0203ae6940900a0a8b76e Author: Lorenzo Miniero Date: Tue Jan 26 15:00:39 2021 +0100 Configurable media direction when putting calls on-hold (SIP plugin) (#2525) commit 7fb08c202b9a087dd51783f5ed7b64345ae0e68e Author: Lorenzo Miniero Date: Tue Jan 26 12:40:26 2021 +0100 Added starting DTLS MTU to info returned by Janus API commit 4d97028dfd46a916c35da658cf7d73c6ae2e02d4 Author: Sami Kuhmonen Date: Tue Jan 26 12:53:14 2021 +0200 Report fail if binding to a socket fails in websockets (#2534) commit 674367adb40315229b4f0ab45b7adf33cbe45fda Author: Evgeniy Baranov Date: Mon Jan 25 12:00:37 2021 +0300 fix race condition in audiobridge plugin changeroom request (#2535) commit 3edb78095bac00bc54a824beb704bfa9075918d0 Author: Alberto Gonzalez Trastoy Date: Sat Jan 23 14:01:27 2021 -0500 Janus npm types upgrade (#2528) commit 78434aa1cba88d982e71f005d0b1943951ff972b Author: August Black Date: Sat Jan 23 12:00:52 2021 -0700 set webrtc-adapter verstion to 7.4.0 (#2531) commit 46a6c71b0fb7e54cb9acfbec84391c75eb224c13 Author: Lorenzo Miniero Date: Thu Jan 21 13:02:06 2021 +0100 Reduced verbosity of a few LOG_WARN messages at startup commit 34f6f892646133a0e2b526a119bdc1e5cbc26564 Author: Andrew Lavrentev Date: Thu Jan 21 14:48:20 2021 +0300 Feature/enhance typings (#2518) commit 16173af36f9e0644f3cfc7518c05f6a53438d0dc Author: RĂ©mi Vansteelandt Date: Thu Jan 21 05:39:17 2021 -0500 Fixed secret authentication on GET requests (#2524) commit 62d75ab3a9cd1cfc5e3f55361dd9d353e12fdd17 Author: Nadin Zajimovic Date: Wed Jan 20 11:36:43 2021 +0100 Dont send bye on early dialog (#2521) commit 2141d9ba6996299f7bd1f4e01b1d751c882a5866 Author: Yurii Cherniavskyi Date: Wed Jan 20 12:30:30 2021 +0200 Update Webpack instruction after webrtc-adapter dependency update (#2519) commit f994f7c19f9becf0eae6352f800af56b033bae45 Author: fbellet Date: Wed Jan 20 11:28:17 2021 +0100 Close nice agent resources asynchronously (#2492) commit 79038e063681bffb19348c20a0cbbe427a048e91 Author: Sergey Radionov Date: Tue Jan 19 18:16:03 2021 +0700 mqttevh: tls support implementation finished (#2517) * mqttevh: tls support implementation finished * mqttevh: MQTTASYNC_OPERATION_INCOMPLETE is not error * mqttevh: allow send messages while connecting is still in progress commit 97cd054d8efbd85b2ebc2d32cb6855a0c6495a21 Author: Lorenzo Miniero Date: Mon Jan 18 11:24:48 2021 +0100 Fixed broken webrtc-adapter links (see #2515) commit c0570a91117aa1292ae5e876301ab5a7f4b4ba08 Author: Tristan Matthews Date: Thu Jan 14 13:24:48 2021 -0500 html: update webrtc-adapter to 7.7.0 (#2515) commit f57215aa71c283f32508ab71653e58b6469008b5 Author: Lorenzo Miniero Date: Thu Jan 14 10:11:57 2021 +0100 Updated year in demos and docs commit bbdd3e4d085cc338b7907c852e13af958faf2686 Author: Chris Wiggins Date: Tue Nov 17 11:49:42 2020 +1300 Adds back in default outgoing queue behaviour. Adds support for auto-generated queue_names commit ed1b5c6fcce36a50acf45f00e69aa2879fe64535 Author: Chris Wiggins Date: Thu Nov 12 13:21:08 2020 +1300 Adds RabbitMQ options for queues, durable, exclusive and autodelete commit 24594f74630567f72d3ccb0e6b80de09afb3242c Author: Chris Wiggins Date: Wed Nov 11 18:05:24 2020 +1300 Check RabbitMQ admin topic in a better way commit 319c6fc265c8ed4310f554a4cd0edc0661553de9 Author: Chris Wiggins Date: Wed Nov 11 16:09:26 2020 +1300 Increase RabbitMQ logging on publish commit 505eeef9d9de4e23f62a7db88cbd08a7863639cf Author: Chris Wiggins Date: Tue Nov 10 18:29:59 2020 +1300 Fix queue_name_admin in rabbitmq transport commit b3f7ad956a98a5790498b107c57af4fda2f29a2f Author: Chris Wiggins Date: Tue Nov 10 18:19:07 2020 +1300 Update rabbitmq logging information commit f604aeb76c71f7d93e131cb9db974d6e804af537 Author: Chris Wiggins Date: Tue Nov 10 17:23:11 2020 +1300 Updates RabbitMQ logic - Publishing to a topic does not require an outgoing queue, just the topic, so the outgoing queues are no longer declared - When the janus_exchange_type is topic, we want to be able to name the queue, and then bind an incoming topic from the exchange to that queue, so that functionality has been added - This is all backwards compatible with original logic, and won't break existing logic --- .github/workflows/janus-ci.yml | 4 +- CHANGELOG.md | 17 + auth.c | 2 +- bower.json | 4 +- conf/janus.eventhandler.mqttevh.jcfg.sample | 6 +- ...janus.eventhandler.rabbitmqevh.jcfg.sample | 3 +- conf/janus.jcfg.sample.in | 12 +- conf/janus.transport.rabbitmq.jcfg.sample | 28 +- configure.ac | 21 +- docs/footer.html | 4 +- docs/janus-doxygen.cfg | 2 +- dtls-bio.c | 3 + dtls-bio.h | 5 +- dtls.c | 2 +- events/janus_mqttevh.c | 41 +- events/janus_rabbitmqevh.c | 29 +- html/audiobridgetest.html | 2 +- html/canvas.html | 2 +- html/devicetest.html | 2 +- html/e2etest.html | 2 +- html/echotest.html | 2 +- html/footer.html | 2 +- html/janus.js | 2 + html/multiopus.html | 2 +- html/nosiptest.html | 2 +- html/recordplaytest.html | 2 +- html/recordplaytest.js | 36 +- html/screensharingtest.html | 2 +- html/siptest.html | 2 +- html/streamingtest.html | 2 +- html/textroomtest.html | 2 +- html/videocalltest.html | 2 +- html/videoroomtest.html | 2 +- html/videoroomtest.js | 9 +- html/voicemailtest.html | 2 +- html/vp9svctest.html | 2 +- ice.c | 73 +++- ice.h | 18 +- janus.c | 22 +- janus.ggo | 2 +- mainpage.dox | 6 +- npm/janus.d.ts | 36 +- package.json | 2 +- plugins/janus_audiobridge.c | 15 +- plugins/janus_duktape.c | 52 +-- plugins/janus_duktape_data.h | 2 +- plugins/janus_lua.c | 52 +-- plugins/janus_lua_data.h | 2 +- plugins/janus_recordplay.c | 70 +++- plugins/janus_sip.c | 135 +++++-- plugins/janus_streaming.c | 2 + plugins/janus_videoroom.c | 375 ++++++++++++++++-- postprocessing/janus-pp-rec.c | 44 +- postprocessing/janus-pp-rec.ggo | 2 +- postprocessing/pcap2mjr.ggo | 2 +- postprocessing/pp-av1.c | 7 +- postprocessing/pp-rtp.h | 3 + record.c | 35 +- record.h | 10 + rtcp.c | 2 +- sctp.c | 2 +- sdp-utils.c | 104 ++--- sdp.c | 1 + transports/janus_http.c | 20 +- transports/janus_nanomsg.c | 4 +- transports/janus_pfunix.c | 6 +- transports/janus_rabbitmq.c | 246 +++++++++--- transports/janus_websockets.c | 28 +- 68 files changed, 1269 insertions(+), 375 deletions(-) diff --git a/.github/workflows/janus-ci.yml b/.github/workflows/janus-ci.yml index 4806fedd79..35b8a9561f 100644 --- a/.github/workflows/janus-ci.yml +++ b/.github/workflows/janus-ci.yml @@ -76,11 +76,11 @@ jobs: if: ${{ matrix.deps_from_src == 'yes' }} uses: actions/setup-python@v2 with: - python-version: '3.x' + python-version: '3.8.5' architecture: 'x64' - name: install python packages if: ${{ matrix.deps_from_src == 'yes' }} - run: pip3 install wheel meson + run: pip3 install -Iv wheel meson==0.54.3 - name: setup libnice from sources if: ${{ matrix.deps_from_src == 'yes' }} run: | diff --git a/CHANGELOG.md b/CHANGELOG.md index c1cf3461d2..5d7a161de7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,23 @@ All notable changes to this project will be documented in this file. +## [v0.10.10] - 2021-02-06 + +- Reduced verbosity of a few LOG_WARN messages at startup +- Close libnice agent resources asynchronously when hanging up PeerConnections (thanks @fbellet!) [[PR-2492](#2492)] +- Fixed broken parsing of SDP when trying to match specific codec profiles [[PR-2549](#2549)] +- Added muting/moderation API to the VideoRoom plugin [[PR-2513](#2513)] +- Fixed a few race conditions in VideoRoom plugin that could lead to crashes [[PR-2539][#2539)] +- Send 480 instead of BYE when hanging up calls in early dialog in the SIP plugin (thanks @zayim!) [[PR-2521](#2521)] +- Added configurable media direction when putting calls on-hold in the SIP plugin [[PR-2525](#2525)] +- Fixed rare race condition in AudioBridge when using "changeroom" (thanks @JeckLabs!) [[PR-2535][#2535)] +- Fixed broken API secret management in HTTP long polls (thanks @remvst!) [[PR-2524](#2524)] +- Report failure if binding to a socket fails in WebSockets transport plugin (thanks @Symbiatch!) [[PR-2534](#2534)] +- Updated RabbitMQ logic in both transport and event handler (thanks @chriswiggins!) [[PR-2430](#2430)] +- Fixed segfault in WebSocket event handler when backend was unreachable +- Added TLS support to MQTT event handler (thanks @RSATom!) [[PR-2517](#2517)] +- Other smaller fixes and improvements (thanks to all who contributed pull requests and reported issues!) + ## [v0.10.9] - 2020-12-23 - Replaced Travis CI with GitHub Actions [[PR-2486](#2486)] diff --git a/auth.c b/auth.c index 7487953377..cbf8b06768 100644 --- a/auth.c +++ b/auth.c @@ -57,7 +57,7 @@ void janus_auth_init(gboolean enabled, const char *secret) { auth_enabled = TRUE; } } else { - JANUS_LOG(LOG_WARN, "Token based authentication disabled\n"); + JANUS_LOG(LOG_INFO, "Token based authentication disabled\n"); } janus_mutex_init(&mutex); } diff --git a/bower.json b/bower.json index 1901156815..a0d7c5acba 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "janus-gateway", - "version": "0.10.10", + "version": "0.11.1", "homepage": "https://github.com/meetecho/janus-gateway", "authors": [ "Lorenzo Miniero ", @@ -33,6 +33,6 @@ "tests" ], "dependencies": { - "webrtc-adapter": "6.4.0" + "webrtc-adapter": "7.7.0" } } diff --git a/conf/janus.eventhandler.mqttevh.jcfg.sample b/conf/janus.eventhandler.mqttevh.jcfg.sample index 1dd93d40c7..748559bd06 100644 --- a/conf/janus.eventhandler.mqttevh.jcfg.sample +++ b/conf/janus.eventhandler.mqttevh.jcfg.sample @@ -15,7 +15,7 @@ general: { json = "indented" # Whether the JSON messages should be indented (default), # plain (no indentation) or compact (no indentation and no spaces) - url = "tcp://localhost:1883" # The URL of the MQTT server. Only tcp supported at this time. + url = "tcp://localhost:1883" # The URL of the MQTT server. "tcp://" and "ssl://" protocols are supported. #mqtt_version = "3.1.1" # Protocol version. Available values: 3.1, 3.1.1 (default), 5. client_id = "janus.example.com" # Janus client id. You have to configure a unique ID (default: guest). #keep_alive_interval = 20 # Keep connection for N seconds (default: 30) @@ -30,6 +30,8 @@ general: { #topic = "/janus/events" # Base topic (default: /janus/events) #addevent = true # Whether we should add the event type to the base topic + #tls_enable = false # Whether TLS support must be enabled + # Initial message sent to status topic #connect_status = "{\"event\": \"connected\", \"eventhandler\": \"janus.eventhandler.mqttevh\"}" # Message sent after disconnect or as LWT @@ -43,7 +45,7 @@ general: { #tls_verify_peer = true # Whether peer verification must be enabled #tls_verify_hostname = true # Whether hostname verification must be enabled - # Certificates to use when SSL support is enabled, if needed + # Certificates to use when TLS support is enabled, if needed #tls_cacert = "/path/to/cacert.pem" #tls_client_cert = "/path/to/cert.pem" #tls_client_key = "/path/to/key.pem" diff --git a/conf/janus.eventhandler.rabbitmqevh.jcfg.sample b/conf/janus.eventhandler.rabbitmqevh.jcfg.sample index 486eaa0976..a29ec4e3cb 100644 --- a/conf/janus.eventhandler.rabbitmqevh.jcfg.sample +++ b/conf/janus.eventhandler.rabbitmqevh.jcfg.sample @@ -18,9 +18,10 @@ general: { #password = "guest" # Password to use to authenticate, if needed #vhost = "/" # Virtual host to specify when logging in, if needed #exchange = "janus-exchange" - route_key = "janus-events" # Name of the queue for event messages + route_key = "janus-events" # Routing key to use when publishing messages #exchange_type = "fanout" # Rabbitmq exchange_type can be one of the available types: direct, topic, headers and fanout (fanout by defualt). #heartbeat = 60 # Defines the seconds without communication that should pass before considering the TCP connection has unreachable. + #declare_outgoing_queue = true # By default (for backwards compatibility), we declare an outgoing queue. Set this to false to disable that behavior #ssl_enable = false # Whether ssl support must be enabled #ssl_verify_peer = true # Whether peer verification must be enabled diff --git a/conf/janus.jcfg.sample.in b/conf/janus.jcfg.sample.in index 7b9c384062..d1a4a0b518 100644 --- a/conf/janus.jcfg.sample.in +++ b/conf/janus.jcfg.sample.in @@ -239,7 +239,15 @@ media: { # configured to do full-trickle (Janus also trickles its candidates to # users) rather than the default half-trickle (Janus supports trickle # candidates from users, but sends its own within the SDP), and whether -# it should work in ICE-Lite mode (by default it doesn't). Finally, +# it should work in ICE-Lite mode (by default it doesn't). If libnice is +# at least 0.1.15, you can choose which ICE nomination mode to use: valid +# values are "regular" and "aggressive" (the default depends on the libnice +# version itself; if we can set it, we set regular nomination). You can +# also configure whether to use connectivity checks as keep-alives, which +# might help detecting when a peer is no longer available (notice that +# current libnice master is breaking connections after 50 seconds when +# keepalive-conncheck is being used, so if you want to use it, better +# sticking to 0.1.18 until the issue is addressed upstream). Finally, # you can also enable ICE-TCP support (beware that this may lead to problems # if you do not enable ICE Lite as well), choose which interfaces should # be used for gathering candidates, and enable or disable the @@ -249,6 +257,8 @@ nat: { #stun_port = 3478 nice_debug = false #full_trickle = true + #ice_nomination = "regular" + #ice_keepalive_conncheck = true #ice_lite = true #ice_tcp = true diff --git a/conf/janus.transport.rabbitmq.jcfg.sample b/conf/janus.transport.rabbitmq.jcfg.sample index c39020731d..9aae80ef0a 100644 --- a/conf/janus.transport.rabbitmq.jcfg.sample +++ b/conf/janus.transport.rabbitmq.jcfg.sample @@ -24,11 +24,18 @@ general: { #username = "guest" # Username to use to authenticate, if needed #password = "guest" # Password to use to authenticate, if needed #vhost = "/" # Virtual host to specify when logging in, if needed - to_janus = "to-janus" # Name of the queue for incoming messages - from_janus = "from-janus" # Name of the queue for outgoing messages + #janus_exchange = "janus-exchange" # Exchange for outgoing messages, using default if not provided - #janus_exchange_type = "fanout" # Rabbitmq exchange_type can be one of the available types: direct, topic, headers and fanout (fanout by defualt). - #ssl_enabled = false # Whether ssl support must be enabled + #janus_exchange_type = "fanout" # Rabbitmq exchange_type can be one of the available types: direct, topic, headers and fanout (fanout by defualt). + #queue_name = "janus-gateway" # Queue name for incoming messages (if set and janus_exchange_type is topic/direct, to_janus will be the routing key the queue is bound to the exchange on) + to_janus = "to-janus" # Name of the queue for incoming messages if queue_name isn't set, otherwise, the routing key that queue_name is bound to + from_janus = "from-janus" # Routing key of the message sent from janus (as well as the name of the outgoing queue if declare_outgoing_queue = true) + #declare_outgoing_queue = true # By default (for backwards compatibility), we declare an outgoing queue. Set this to false to disable that behavior + #queue_durable = false # Whether or not incoming queue should remain after a RabbitMQ reboot + #queue_autodelete = false # Whether or not incoming queue should autodelete after janus disconnects from RabbitMQ + #queue_exclusive = false # Whether or not incoming queue should only allow one subscriber + + #ssl_enabled = false # Whether ssl support must be enabled #ssl_verify_peer = true # Whether peer verification must be enabled #ssl_verify_hostname = true # Whether hostname verification must be enabled @@ -43,7 +50,14 @@ general: { # Admin API messaging. The same RabbitMQ server is supposed to be used. # Notice that by default the Admin API support via RabbitMQ is disabled. admin: { - #admin_enabled = false # Whether the support must be enabled - #to_janus_admin = "to-janus-admin" # Name of the queue for incoming messages - #from_janus_admin = "from-janus-admin" # Name of the queue for outgoing messages + #admin_enabled = false # Whether the support must be enabled + + #queue_name_admin = "janus-gateway-admin" # Queue name for incoming admin messages (if set and janus_exchange_type is topic/direct, to_janus_admin will be the the routing key the queue is bound to the exchange on) + #to_janus_admin = "to-janus-admin" # Name of the queue for incoming messages if queue_name_admin isn't set, otherwise, the routing key that queue_name_admin is bound to + #from_janus_admin = "from-janus-admin" # Routing key of the message sent from janus (as well as the name of the outgoing queue if declare_outgoing_queue_admin = true) + #declare_outgoing_queue_admin = true # By default (for backwards compatibility), we declare an outgoing queue. Set this to false to disable that behavior + #queue_durable_admin = false # Whether or not incoming queue should remain after a RabbitMQ reboot + #queue_autodelete_admin = false # Whether or not incoming queue should autodelete after janus disconnects from RabbitMQ + #queue_exclusive_admin = false # Whether or not incoming queue should only allow one subscriber + } diff --git a/configure.ac b/configure.ac index e29cadd2a3..f305d52759 100644 --- a/configure.ac +++ b/configure.ac @@ -1,4 +1,4 @@ -AC_INIT([Janus WebRTC Server],[0.10.10],[https://github.com/meetecho/janus-gateway],[janus-gateway],[https://janus.conf.meetecho.com]) +AC_INIT([Janus WebRTC Server],[0.11.1],[https://github.com/meetecho/janus-gateway],[janus-gateway],[https://janus.conf.meetecho.com]) AC_LANG(C) AC_CONFIG_AUX_DIR([.]) AC_CONFIG_MACRO_DIR([m4]) @@ -51,7 +51,8 @@ CFLAGS="$CFLAGS \ -Wstrict-prototypes \ -Wswitch-default \ -Wunused \ - -Wwrite-strings" + -Wwrite-strings \ + -Werror=implicit-function-declaration" case "$CC" in clang*) @@ -69,9 +70,9 @@ clang*) -Wunused-but-set-variable" esac -JANUS_VERSION=110 +JANUS_VERSION=111 AC_SUBST(JANUS_VERSION) -JANUS_VERSION_STRING="0.10.10" +JANUS_VERSION_STRING="0.11.1" AC_SUBST(JANUS_VERSION_STRING) case "$host_os" in @@ -395,6 +396,18 @@ AC_CHECK_LIB([nice], [AC_MSG_NOTICE([libnice version does not support TCP candidates])] ) +AC_CHECK_LIB([nice], + [nice_agent_close_async], + [AC_DEFINE(HAVE_CLOSE_ASYNC)], + [AC_MSG_NOTICE([libnice version does not have nice_agent_close_async])] + ) + +AC_CHECK_LIB([nice], + [nice_agent_new_full], + [AC_DEFINE(HAVE_ICE_NOMINATION)], + [AC_MSG_NOTICE([libnice version does not have nice_agent_new_full])] + ) + AC_CHECK_LIB([dl], [dlopen], [JANUS_MANUAL_LIBS="${JANUS_MANUAL_LIBS} -ldl"], diff --git a/docs/footer.html b/docs/footer.html index 451b4f8498..0cdf6127e5 100644 --- a/docs/footer.html +++ b/docs/footer.html @@ -5,7 +5,7 @@
    $navpath
@@ -13,7 +13,7 @@ diff --git a/docs/janus-doxygen.cfg b/docs/janus-doxygen.cfg index 55c6036de7..601adc97f2 100644 --- a/docs/janus-doxygen.cfg +++ b/docs/janus-doxygen.cfg @@ -38,7 +38,7 @@ PROJECT_NAME = "Janus" # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 0.10.10 +PROJECT_NUMBER = 0.11.1 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/dtls-bio.c b/dtls-bio.c index 6e118eeb93..f566b29ffb 100644 --- a/dtls-bio.c +++ b/dtls-bio.c @@ -25,6 +25,9 @@ void janus_dtls_bio_agent_set_mtu(int start_mtu) { mtu = start_mtu; JANUS_LOG(LOG_VERB, "Setting starting MTU in the DTLS BIO writer: %d\n", mtu); } +int janus_dtls_bio_agent_get_mtu(void) { + return mtu; +} /* BIO implementation */ static int janus_dtls_bio_agent_write(BIO *h, const char *buf, int num); diff --git a/dtls-bio.h b/dtls-bio.h index ce18413064..92f06d7c0a 100644 --- a/dtls-bio.h +++ b/dtls-bio.h @@ -27,9 +27,12 @@ BIO *BIO_janus_dtls_agent_new(void *dtls); * you know for sure the MTU in the network Janus is deployed in is * smaller than that, it makes sense to configure an according value to * start from - * @param start_mtu The MTU to start from (1472 by default) + * @param start_mtu The MTU to start from (1200 by default) */ void janus_dtls_bio_agent_set_mtu(int start_mtu); +/*! \brief Return which MTU was configured for the BIO agent writer + * @returns The MTU the stack will start from for each session */ +int janus_dtls_bio_agent_get_mtu(void); #if defined(LIBRESSL_VERSION_NUMBER) #define JANUS_USE_OPENSSL_PRE_1_1_API (1) diff --git a/dtls.c b/dtls.c index b61b0b49dd..6abc6b99da 100644 --- a/dtls.c +++ b/dtls.c @@ -421,7 +421,7 @@ gint janus_dtls_srtp_init(const char *server_pem, const char *server_key, const #endif if(!server_pem && !server_key) { - JANUS_LOG(LOG_WARN, "No cert/key specified, autogenerating some...\n"); + JANUS_LOG(LOG_INFO, "No cert/key specified, autogenerating some...\n"); if(janus_dtls_generate_keys(&ssl_cert, &ssl_key, rsa_private_key) != 0) { JANUS_LOG(LOG_FATAL, "Error generating DTLS key/certificate\n"); return -2; diff --git a/events/janus_mqttevh.c b/events/janus_mqttevh.c index 43d1805416..7be87a6eb7 100644 --- a/events/janus_mqttevh.c +++ b/events/janus_mqttevh.c @@ -379,6 +379,15 @@ static int janus_mqttevh_client_connect(janus_mqttevh_context *ctx) { options.keepAliveInterval = ctx->connect.keep_alive_interval; options.maxInflight = ctx->connect.max_inflight; + MQTTAsync_SSLOptions ssl_opts = MQTTAsync_SSLOptions_initializer; + if(ctx->tls.enable) { + ssl_opts.trustStore = ctx->tls.cacert_file; + ssl_opts.keyStore = ctx->tls.cert_file; + ssl_opts.privateKey = ctx->tls.key_file; + ssl_opts.enableServerCertAuth = ctx->tls.verify_peer; + options.ssl = &ssl_opts; + } + MQTTAsync_willOptions willOptions = MQTTAsync_willOptions_initializer; if(ctx->will.enabled) { willOptions.topicName = ctx->will.topic; @@ -535,10 +544,14 @@ static int janus_mqttevh_client_publish_message(janus_mqttevh_context *ctx, cons options.onFailure = janus_mqttevh_client_publish_message_failure; rc = MQTTAsync_sendMessage(ctx->client, topic, &msg, &options); - if(rc == MQTTASYNC_SUCCESS) { - JANUS_LOG(LOG_HUGE, "MQTT EVH message sent to topic %s on %s. Result %d\n", topic, ctx->connect.url, rc); - } else { - JANUS_LOG(LOG_WARN, "FAILURE: MQTT EVH message propably not sent to topic %s on %s. Result %d\n", topic, ctx->connect.url, rc); + switch(rc) { + case MQTTASYNC_SUCCESS: + JANUS_LOG(LOG_HUGE, "MQTT EVH message sent to topic %s on %s. Result %d\n", topic, ctx->connect.url, rc); + break; + case MQTTASYNC_OPERATION_INCOMPLETE: + break; + default: + JANUS_LOG(LOG_WARN, "FAILURE: MQTT EVH message propably not sent to topic %s on %s. Result %d\n", topic, ctx->connect.url, rc); } return rc; @@ -561,10 +574,14 @@ static int janus_mqttevh_client_publish_message5(janus_mqttevh_context *ctx, con options.onFailure5 = janus_mqttevh_client_publish_message_failure5; rc = MQTTAsync_sendMessage(ctx->client, topic, &msg, &options); - if(rc == MQTTASYNC_SUCCESS) { - JANUS_LOG(LOG_HUGE, "MQTT EVH message sent to topic %s on %s. Result %d\n", topic, ctx->connect.url, rc); - } else { - JANUS_LOG(LOG_WARN, "FAILURE: MQTT EVH message propably not sent to topic %s on %s. Result %d\n", topic, ctx->connect.url, rc); + switch(rc) { + case MQTTASYNC_SUCCESS: + JANUS_LOG(LOG_HUGE, "MQTT EVH message sent to topic %s on %s. Result %d\n", topic, ctx->connect.url, rc); + break; + case MQTTASYNC_OPERATION_INCOMPLETE: + break; + default: + JANUS_LOG(LOG_WARN, "FAILURE: MQTT EVH message propably not sent to topic %s on %s. Result %d\n", topic, ctx->connect.url, rc); } return rc; @@ -604,7 +621,12 @@ static void janus_mqttevh_client_publish_message_failure5(void *context, MQTTAsy static void janus_mqttevh_client_publish_message_failure_impl(void *context, int rc) { janus_mqttevh_context *ctx = (janus_mqttevh_context *)context; - JANUS_LOG(LOG_ERR, "MQTT EVH client has failed publishing to MQTT topic: %s, return code: %d\n", ctx->publish.topic, rc); + switch(rc) { + case MQTTASYNC_OPERATION_INCOMPLETE: + break; + default: + JANUS_LOG(LOG_ERR, "MQTT EVH client has failed publishing to MQTT topic: %s, return code: %d\n", ctx->publish.topic, rc); + } } /* Destroy Janus MQTT event handler session context */ @@ -983,6 +1005,7 @@ static int janus_mqttevh_init(const char *config_path) { create_options.maxBufferedMessages = ctx->connect.max_buffered; + create_options.sendWhileDisconnected = TRUE; res = MQTTAsync_createWithOptions( &ctx->client, ctx->connect.url, diff --git a/events/janus_rabbitmqevh.c b/events/janus_rabbitmqevh.c index 0cf269d89f..7a078e12e3 100644 --- a/events/janus_rabbitmqevh.c +++ b/events/janus_rabbitmqevh.c @@ -100,7 +100,6 @@ static size_t json_format = JSON_INDENT(3) | JSON_PRESERVE_ORDER; static amqp_connection_state_t rmq_conn; static amqp_channel_t rmq_channel = 0; static amqp_bytes_t rmq_exchange; -static amqp_bytes_t rmq_route_key; static janus_mutex mutex; @@ -115,6 +114,7 @@ static gboolean ssl_verify_hostname = FALSE; static const char *route_key = NULL, *exchange = NULL, *exchange_type = NULL ; static uint16_t heartbeat = 0; static uint16_t rmqport = AMQP_PROTOCOL_PORT; +static gboolean declare_outgoing_queue = TRUE; /* Parameter validation (for tweaking via Admin API) */ static struct janus_json_parameter request_parameters[] = { @@ -266,6 +266,12 @@ int janus_rabbitmqevh_init(const char *config_path) { exchange_type = g_strdup(item->value); } + /* By default we *DO* declare the outgoing queue */ + item = janus_config_get(config, config_general, janus_config_type_item, "declare_outgoing_queue"); + if(item && item->value && !janus_is_true(item->value)) { + declare_outgoing_queue = FALSE; + } + item = janus_config_get(config, config_general, janus_config_type_item, "exchange"); if(!item || !item->value) { JANUS_LOG(LOG_INFO, "RabbitMQEventHandler: Missing name of outgoing exchange for RabbitMQ, using default\n"); @@ -408,14 +414,17 @@ int janus_rabbitmqevh_connect(void) { return -1; } } - JANUS_LOG(LOG_VERB, "Declaring outgoing queue... (%s)\n", route_key); - rmq_route_key = amqp_cstring_bytes(route_key); - amqp_queue_declare(rmq_conn, rmq_channel, rmq_route_key, 0, 0, 0, 0, amqp_empty_table); - result = amqp_get_rpc_reply(rmq_conn); - if(result.reply_type != AMQP_RESPONSE_NORMAL) { - JANUS_LOG(LOG_FATAL, "RabbitMQEventHandler: Can't connect to RabbitMQ server: error declaring queue... %s, %s\n", amqp_error_string2(result.library_error), amqp_method_name(result.reply.id)); - return -1; + + if (declare_outgoing_queue) { + JANUS_LOG(LOG_VERB, "Declaring outgoing queue... (%s)\n", route_key); + amqp_queue_declare(rmq_conn, rmq_channel, amqp_cstring_bytes(route_key), 0, 0, 0, 0, amqp_empty_table); + result = amqp_get_rpc_reply(rmq_conn); + if(result.reply_type != AMQP_RESPONSE_NORMAL) { + JANUS_LOG(LOG_FATAL, "RabbitMQEventHandler: Can't connect to RabbitMQ server: error declaring queue... %s, %s\n", amqp_error_string2(result.library_error), amqp_method_name(result.reply.id)); + return -1; + } } + return 0; } @@ -444,8 +453,6 @@ void janus_rabbitmqevh_destroy(void) { } if(rmq_exchange.bytes) g_free((char *)rmq_exchange.bytes); - if(rmq_route_key.bytes) - g_free((char *)rmq_route_key.bytes); if(rmqhost) g_free((char *)rmqhost); if(vhost) @@ -611,7 +618,7 @@ static void *jns_rmqevh_hdlr(void *data) { props.content_type = amqp_cstring_bytes("application/json"); amqp_bytes_t message = amqp_cstring_bytes(event_text); janus_mutex_lock(&mutex); - int status = amqp_basic_publish(rmq_conn, rmq_channel, rmq_exchange, rmq_route_key, 0, 0, &props, message); + int status = amqp_basic_publish(rmq_conn, rmq_channel, rmq_exchange, amqp_cstring_bytes(route_key), 0, 0, &props, message); if(status != AMQP_STATUS_OK) { JANUS_LOG(LOG_ERR, "RabbitMQEventHandler: Error publishing... %d, %s\n", status, amqp_error_string2(status)); } diff --git a/html/audiobridgetest.html b/html/audiobridgetest.html index d46f86480d..6621aca885 100644 --- a/html/audiobridgetest.html +++ b/html/audiobridgetest.html @@ -5,7 +5,7 @@ Janus WebRTC Server: Audio Bridge Demo - + diff --git a/html/canvas.html b/html/canvas.html index 1cd020b31c..7bd9af908a 100644 --- a/html/canvas.html +++ b/html/canvas.html @@ -5,7 +5,7 @@ Janus WebRTC Server: Canvas Capture - + diff --git a/html/devicetest.html b/html/devicetest.html index 945a88074d..3e645c5099 100644 --- a/html/devicetest.html +++ b/html/devicetest.html @@ -5,7 +5,7 @@ Janus WebRTC Server: Device Selection Test - + diff --git a/html/e2etest.html b/html/e2etest.html index 47dbb2b20b..adef1688d6 100644 --- a/html/e2etest.html +++ b/html/e2etest.html @@ -5,7 +5,7 @@ Janus WebRTC Server: End-to-end Encryption Test - + diff --git a/html/echotest.html b/html/echotest.html index 3455c581fa..47d7f6866f 100644 --- a/html/echotest.html +++ b/html/echotest.html @@ -5,7 +5,7 @@ Janus WebRTC Server: Echo Test - + diff --git a/html/footer.html b/html/footer.html index 7ee68e77ec..fadbf55854 100644 --- a/html/footer.html +++ b/html/footer.html @@ -1 +1 @@ -

Janus WebRTC Server © Meetecho 2014-2020

+

Janus WebRTC Server © Meetecho 2014-2021

diff --git a/html/janus.js b/html/janus.js index a3a30f58d7..66ca06cd5b 100644 --- a/html/janus.js +++ b/html/janus.js @@ -2566,6 +2566,7 @@ function Janus(gatewayCallbacks) { callbacks = callbacks || {}; callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop; callbacks.error = (typeof callbacks.error == "function") ? callbacks.error : webrtcError; + callbacks.customizeSdp = (typeof callbacks.customizeSdp == "function") ? callbacks.customizeSdp : Janus.noop; var jsep = callbacks.jsep; var pluginHandle = pluginHandles[handleId]; if(!pluginHandle || !pluginHandle.webrtcStuff) { @@ -2580,6 +2581,7 @@ function Janus(gatewayCallbacks) { callbacks.error("No PeerConnection: if this is an answer, use createAnswer and not handleRemoteJsep"); return; } + callbacks.customizeSdp(jsep); config.pc.setRemoteDescription(jsep) .then(function() { Janus.log("Remote description accepted!"); diff --git a/html/multiopus.html b/html/multiopus.html index df7e152428..89a5e5edbb 100644 --- a/html/multiopus.html +++ b/html/multiopus.html @@ -5,7 +5,7 @@ Janus WebRTC Server: Multichannel Opus (surround) - + diff --git a/html/nosiptest.html b/html/nosiptest.html index 96dae0405a..093bbb382e 100644 --- a/html/nosiptest.html +++ b/html/nosiptest.html @@ -5,7 +5,7 @@ Janus WebRTC Server: NoSIP (SDP/RTP) - + diff --git a/html/recordplaytest.html b/html/recordplaytest.html index d46832603c..b4cc6313f4 100644 --- a/html/recordplaytest.html +++ b/html/recordplaytest.html @@ -5,7 +5,7 @@ Janus WebRTC Server: Recorder/Playout Demo - + diff --git a/html/recordplaytest.js b/html/recordplaytest.js index 37b1ba826a..9000740458 100644 --- a/html/recordplaytest.js +++ b/html/recordplaytest.js @@ -171,19 +171,6 @@ $(document).ready(function() { Janus.log("The ID of the current recording is " + id); recordingId = id; } - } else if(event === 'slow_link') { - var uplink = result["uplink"]; - if(uplink !== 0) { - // Janus detected issues when receiving our media, let's slow down - bandwidth = parseInt(bandwidth / 1.5); - recordplay.send({ - message: { - request: 'configure', - 'video-bitrate-max': bandwidth, // Reduce the bitrate - 'video-keyframe-interval': 15000 // Keep the 15 seconds key frame interval - } - }); - } } else if(event === 'playing') { Janus.log("Playout has started!"); } else if(event === 'stopped') { @@ -290,6 +277,26 @@ $(document).ready(function() { } else { spinner.spin(); } + if($('#curres').length === 0) { + $('#videobox').append( + '' + + ''); + $("#video").bind("playing", function () { + var width = this.videoWidth; + var height = this.videoHeight; + $('#curres').text(width + 'x' + height); + }); + recordplay.bitrateTimer = setInterval(function() { + // Display updated bitrate, if supported + var bitrate = recordplay.getBitrate(); + $('#curbw').text(bitrate); + var video = $('#thevideo').get(0); + var width = video.videoWidth; + var height = video.videoHeight; + if(width > 0 && height > 0) + $('#curres').text(width + 'x' + height); + }, 1000); + } // Show the video, hide the spinner and show the resolution when we get a playing event $("#thevideo").bind("playing", function () { $('#waitingvideo').remove(); @@ -337,6 +344,9 @@ $(document).ready(function() { if(spinner) spinner.stop(); spinner = null; + if(recordplay.bitrateTimer) + clearInterval(recordplay.bitrateTimer); + delete recordplay.bitrateTimer; $('#videobox').empty(); $("#videobox").parent().unblock(); $('#video').hide(); diff --git a/html/screensharingtest.html b/html/screensharingtest.html index be031113fb..de94da5014 100644 --- a/html/screensharingtest.html +++ b/html/screensharingtest.html @@ -5,7 +5,7 @@ Janus WebRTC Server: Screen Sharing Demo - + diff --git a/html/siptest.html b/html/siptest.html index 943625fb1c..bdb11c900c 100644 --- a/html/siptest.html +++ b/html/siptest.html @@ -5,7 +5,7 @@ Janus WebRTC Server: SIP Gateway Demo - + diff --git a/html/streamingtest.html b/html/streamingtest.html index 639c221a77..341615bc1e 100644 --- a/html/streamingtest.html +++ b/html/streamingtest.html @@ -5,7 +5,7 @@ Janus WebRTC Server: Streaming Demo - + diff --git a/html/textroomtest.html b/html/textroomtest.html index 981d184038..8437c7b6bc 100644 --- a/html/textroomtest.html +++ b/html/textroomtest.html @@ -5,7 +5,7 @@ Janus WebRTC Server: Text Room - + diff --git a/html/videocalltest.html b/html/videocalltest.html index f6fabea6be..f2288560c5 100644 --- a/html/videocalltest.html +++ b/html/videocalltest.html @@ -5,7 +5,7 @@ Janus WebRTC Server: Video Call Demo - + diff --git a/html/videoroomtest.html b/html/videoroomtest.html index 5d5d6c1695..607f4ed8a8 100644 --- a/html/videoroomtest.html +++ b/html/videoroomtest.html @@ -5,7 +5,7 @@ Janus WebRTC Server: Video Room Demo - + diff --git a/html/videoroomtest.js b/html/videoroomtest.js index c8a533c5cf..b73bd8cc57 100644 --- a/html/videoroomtest.js +++ b/html/videoroomtest.js @@ -66,6 +66,8 @@ var bitrateTimer = []; var doSimulcast = (getQueryStringValue("simulcast") === "yes" || getQueryStringValue("simulcast") === "true"); var doSimulcast2 = (getQueryStringValue("simulcast2") === "yes" || getQueryStringValue("simulcast2") === "true"); +var acodec = (getQueryStringValue("acodec") !== "" ? getQueryStringValue("acodec") : null); +var vcodec = (getQueryStringValue("vcodec") !== "" ? getQueryStringValue("vcodec") : null); var subscriber_mode = (getQueryStringValue("subscriber-mode") === "yes" || getQueryStringValue("subscriber-mode") === "true"); $(document).ready(function() { @@ -426,7 +428,12 @@ function publishOwnFeed(useAudio) { // a codec will only work if: (1) the codec is actually in the SDP (and // so the browser supports it), and (2) the codec is in the list of // allowed codecs in a room. With respect to the point (2) above, - // refer to the text in janus.plugin.videoroom.jcfg for more details + // refer to the text in janus.plugin.videoroom.jcfg for more details. + // We allow people to specify a codec via query string, for demo purposes + if(acodec) + publish["audiocodec"] = acodec; + if(vcodec) + publish["videocodec"] = vcodec; sfutest.send({ message: publish, jsep: jsep }); }, error: function(error) { diff --git a/html/voicemailtest.html b/html/voicemailtest.html index 7d68ae2afc..d7a205cd46 100644 --- a/html/voicemailtest.html +++ b/html/voicemailtest.html @@ -5,7 +5,7 @@ Janus WebRTC Server: Voice Mail Demo - + diff --git a/html/vp9svctest.html b/html/vp9svctest.html index 8fa0a97d22..9488e363b9 100644 --- a/html/vp9svctest.html +++ b/html/vp9svctest.html @@ -5,7 +5,7 @@ Janus WebRTC Server: VP9-SVC Video Room Demo - + diff --git a/ice.c b/ice.c index a38dbedbbf..8180d619c7 100644 --- a/ice.c +++ b/ice.c @@ -101,6 +101,41 @@ gboolean janus_ice_is_ipv6_enabled(void) { return janus_ipv6_enabled; } +#ifdef HAVE_ICE_NOMINATION +/* Since libnice 0.1.15, we can configure the ICE nomination mode: it was + * always "aggressive" before, we set it to "regular" by default if we can */ +static NiceNominationMode janus_ice_nomination = NICE_NOMINATION_MODE_REGULAR; +void janus_ice_set_nomination_mode(const char *nomination) { + if(nomination == NULL) { + JANUS_LOG(LOG_WARN, "Invalid ICE nomination mode, falling back to 'regular'\n"); + } else if(!strcasecmp(nomination, "regular")) { + JANUS_LOG(LOG_INFO, "Configuring Janus to use ICE regular nomination\n"); + janus_ice_nomination = NICE_NOMINATION_MODE_REGULAR; + } else if(!strcasecmp(nomination, "aggressive")) { + JANUS_LOG(LOG_INFO, "Configuring Janus to use ICE aggressive nomination\n"); + janus_ice_nomination = NICE_NOMINATION_MODE_AGGRESSIVE; + } else { + JANUS_LOG(LOG_WARN, "Unsupported ICE nomination mode '%s', falling back to 'regular'\n", nomination); + } +} +const char *janus_ice_get_nomination_mode(void) { + return (janus_ice_nomination == NICE_NOMINATION_MODE_REGULAR ? "regular" : "aggressive"); +} +#endif + +/* Keepalive via connectivity checks */ +static gboolean janus_ice_keepalive_connchecks = FALSE; +void janus_ice_set_keepalive_conncheck_enabled(gboolean enabled) { + janus_ice_keepalive_connchecks = enabled; + if(janus_ice_keepalive_connchecks) { + JANUS_LOG(LOG_INFO, "Using connectivity checks as PeerConnection keep-alives\n"); + JANUS_LOG(LOG_WARN, "Notice that the current libnice master is breaking connections after 50s when keepalive-conncheck enabled. As such, better to stick to 0.1.18 until the issue is addressed upstream\n"); + } +} +gboolean janus_ice_is_keepalive_conncheck_enabled(void) { + return janus_ice_keepalive_connchecks; +} + /* Opaque IDs set by applications are by default only passed to event handlers * for correlation purposes, but not sent back to the user or application in * the related Janus API responses or events, unless configured otherwise */ @@ -1377,6 +1412,19 @@ static void janus_ice_handle_free(const janus_refcount *handle_ref) { g_free(handle); } +#ifdef HAVE_CLOSE_ASYNC +static void janus_ice_cb_agent_closed(GObject *src, GAsyncResult *result, gpointer data) { + janus_ice_outgoing_traffic *t = (janus_ice_outgoing_traffic *)data; + janus_ice_handle *handle = t->handle; + + JANUS_LOG(LOG_VERB, "[%"SCNu64"] Disposing nice agent %p\n", handle->handle_id, handle->agent); + g_object_unref(handle->agent); + handle->agent = NULL; + g_source_unref((GSource *)t); + janus_refcount_decrease(&handle->ref); +} +#endif + static void janus_ice_plugin_session_free(const janus_refcount *app_handle_ref) { janus_plugin_session *app_handle = janus_refcount_containerof(app_handle_ref, janus_plugin_session, ref); /* This app handle can be destroyed, free all the resources */ @@ -1436,9 +1484,22 @@ static void janus_ice_webrtc_free(janus_ice_handle *handle) { handle->stream = NULL; } if(handle->agent != NULL) { +#ifdef HAVE_CLOSE_ASYNC + if(G_IS_OBJECT(handle->agent)) { + JANUS_LOG(LOG_VERB, "[%"SCNu64"] Removing stream %d from agent %p\n", + handle->handle_id, handle->stream_id, handle->agent); + nice_agent_remove_stream(handle->agent, handle->stream_id); + handle->stream_id = 0; + JANUS_LOG(LOG_VERB, "[%"SCNu64"] Closing nice agent %p\n", handle->handle_id, handle->agent); + janus_refcount_increase(&handle->ref); + g_source_ref(handle->rtp_source); + nice_agent_close_async(handle->agent, janus_ice_cb_agent_closed, handle->rtp_source); + } +#else if(G_IS_OBJECT(handle->agent)) g_object_unref(handle->agent); handle->agent = NULL; +#endif } if(handle->pending_trickles) { while(handle->pending_trickles) { @@ -3435,6 +3496,10 @@ int janus_ice_setup_local(janus_ice_handle *handle, int offer, int audio, int vi "main-context", handle->mainctx, "reliable", FALSE, "full-mode", janus_ice_lite_enabled ? FALSE : TRUE, +#ifdef HAVE_ICE_NOMINATION + "nomination-mode", janus_ice_nomination, +#endif + "keepalive-conncheck", janus_ice_keepalive_connchecks ? TRUE : FALSE, #ifdef HAVE_LIBNICE_TCP "ice-udp", TRUE, "ice-tcp", janus_ice_tcp_enabled ? TRUE : FALSE, @@ -3851,7 +3916,7 @@ static gboolean janus_ice_outgoing_rtcp_handle(gpointer user_data) { /* Create a SR/SDES compound */ int srlen = 28; int sdeslen = 16; - char rtcpbuf[srlen+sdeslen]; + char rtcpbuf[sizeof(janus_rtcp_sr)+sdeslen]; memset(rtcpbuf, 0, sizeof(rtcpbuf)); rtcp_sr *sr = (rtcp_sr *)&rtcpbuf; sr->header.version = 2; @@ -3877,7 +3942,7 @@ static gboolean janus_ice_outgoing_rtcp_handle(gpointer user_data) { } sr->si.s_packets = htonl(stream->component->out_stats.audio.packets); sr->si.s_octets = htonl(stream->component->out_stats.audio.bytes); - rtcp_sdes *sdes = (rtcp_sdes *)&rtcpbuf[28]; + rtcp_sdes *sdes = (rtcp_sdes *)&rtcpbuf[srlen]; janus_rtcp_sdes_cname((char *)sdes, sdeslen, "janus", 5); sdes->chunk.ssrc = htonl(stream->audio_ssrc); /* Enqueue it, we'll send it later */ @@ -3912,7 +3977,7 @@ static gboolean janus_ice_outgoing_rtcp_handle(gpointer user_data) { /* Create a SR/SDES compound */ int srlen = 28; int sdeslen = 16; - char rtcpbuf[srlen+sdeslen]; + char rtcpbuf[sizeof(janus_rtcp_sr)+sdeslen]; memset(rtcpbuf, 0, sizeof(rtcpbuf)); rtcp_sr *sr = (rtcp_sr *)&rtcpbuf; sr->header.version = 2; @@ -3938,7 +4003,7 @@ static gboolean janus_ice_outgoing_rtcp_handle(gpointer user_data) { } sr->si.s_packets = htonl(stream->component->out_stats.video[0].packets); sr->si.s_octets = htonl(stream->component->out_stats.video[0].bytes); - rtcp_sdes *sdes = (rtcp_sdes *)&rtcpbuf[28]; + rtcp_sdes *sdes = (rtcp_sdes *)&rtcpbuf[srlen]; janus_rtcp_sdes_cname((char *)sdes, sdeslen, "janus", 5); sdes->chunk.ssrc = htonl(stream->video_ssrc); /* Enqueue it, we'll send it later */ diff --git a/ice.h b/ice.h index 8fe22c5c33..015e940306 100644 --- a/ice.h +++ b/ice.h @@ -129,6 +129,22 @@ gboolean janus_ice_is_mdns_enabled(void); /*! \brief Method to check whether IPv6 candidates are enabled/supported or not * @returns true if IPv6 candidates are enabled/supported, false otherwise */ gboolean janus_ice_is_ipv6_enabled(void); +#ifdef HAVE_ICE_NOMINATION +/*! \brief Method to configure the ICE nomination mode (regular or aggressive) + * @param[in] nomination The ICE nomination mode (regular or aggressive) */ +void janus_ice_set_nomination_mode(const char *nomination); +/*! \brief Method to return a string description of the configured ICE nomination mode + * @returns "regular" or "aggressive" */ +const char *janus_ice_get_nomination_mode(void); +#endif +/*! \brief Method to enable/disable connectivity checks as keepalives for PeerConnections. + * \note The main rationale behind this setting is provided in the libnice documentation: + * https://libnice.freedesktop.org/libnice/NiceAgent.html#NiceAgent--keepalive-conncheck + * @param[in] enabled Whether the functionality should be enabled or disabled */ +void janus_ice_set_keepalive_conncheck_enabled(gboolean enabled); +/*! \brief Method to check whether connectivity checks will be used as keepalives + * @returns true if enabled, false (default) otherwise */ +gboolean janus_ice_is_keepalive_conncheck_enabled(void); /*! \brief Method to modify the min NACK value (i.e., the minimum time window of packets per handle to store for retransmissions) * @param[in] mnq The new min NACK value */ void janus_set_min_nack_queue(uint16_t mnq); @@ -140,7 +156,7 @@ uint16_t janus_get_min_nack_queue(void); * keyframe, as any missing packet won't be needed since the keyframe will allow the * media recipient to still restore a complete image anyway. Since this optimization * seems to cause some issues in some edge cases, it's disabled by default. - * @param[in] optimize Whether the opzimization should be enabled or disabled */ + * @param[in] optimize Whether the optimization should be enabled or disabled */ void janus_set_nack_optimizations_enabled(gboolean optimize); /*! \brief Method to check whether NACK optimizations on outgoing keyframes are enabled or not * @returns optimize if optimizations are enabled, false otherwise */ diff --git a/janus.c b/janus.c index 144405752f..db1ca89689 100644 --- a/janus.c +++ b/janus.c @@ -346,6 +346,10 @@ static json_t *janus_info(const char *transaction) { json_object_set_new(info, "ipv6", janus_ice_is_ipv6_enabled() ? json_true() : json_false()); json_object_set_new(info, "ice-lite", janus_ice_is_ice_lite_enabled() ? json_true() : json_false()); json_object_set_new(info, "ice-tcp", janus_ice_is_ice_tcp_enabled() ? json_true() : json_false()); +#ifdef HAVE_ICE_NOMINATION + json_object_set_new(info, "ice-nomination", json_string(janus_ice_get_nomination_mode())); +#endif + json_object_set_new(info, "ice-keepalive-conncheck", janus_ice_is_keepalive_conncheck_enabled() ? json_true() : json_false()); json_object_set_new(info, "full-trickle", janus_ice_is_full_trickle_enabled() ? json_true() : json_false()); json_object_set_new(info, "mdns-enabled", janus_ice_is_mdns_enabled() ? json_true() : json_false()); json_object_set_new(info, "min-nack-queue", json_integer(janus_get_min_nack_queue())); @@ -353,6 +357,7 @@ static json_t *janus_info(const char *transaction) { json_object_set_new(info, "twcc-period", json_integer(janus_get_twcc_period())); if(janus_get_dscp() > 0) json_object_set_new(info, "dscp", json_integer(janus_get_dscp())); + json_object_set_new(info, "dtls-mtu", json_integer(janus_dtls_bio_agent_get_mtu())); if(janus_ice_get_stun_server() != NULL) { char server[255]; g_snprintf(server, 255, "%s:%"SCNu16, janus_ice_get_stun_server(), janus_ice_get_stun_port()); @@ -1531,7 +1536,7 @@ int janus_process_incoming_request(janus_request *request) { if(stream != NULL && stream->component != NULL && stream->component->dtls != NULL && stream->component->dtls->sctp == NULL) { /* Create SCTP association as well */ - JANUS_LOG(LOG_WARN, "[%"SCNu64"] Creating datachannels...\n", handle->handle_id); + JANUS_LOG(LOG_VERB, "[%"SCNu64"] Creating datachannels...\n", handle->handle_id); janus_dtls_srtp_create_sctp(stream->component->dtls); } } @@ -3790,7 +3795,7 @@ json_t *janus_plugin_handle_sdp(janus_plugin_session *plugin_session, janus_plug if(stream != NULL && stream->component != NULL && stream->component->dtls != NULL && stream->component->dtls->sctp == NULL) { /* Create SCTP association as well */ - JANUS_LOG(LOG_WARN, "[%"SCNu64"] Creating datachannels...\n", ice_handle->handle_id); + JANUS_LOG(LOG_VERB, "[%"SCNu64"] Creating datachannels...\n", ice_handle->handle_id); janus_dtls_srtp_create_sctp(stream->component->dtls); } } @@ -4786,6 +4791,17 @@ gint main(int argc, char *argv[]) JANUS_LOG(LOG_ERR, "Invalid STUN address %s:%u. STUN will be disabled\n", stun_server, stun_port); } } + item = janus_config_get(config, config_nat, janus_config_type_item, "ice_nomination"); + if(item && item->value) { +#ifndef HAVE_ICE_NOMINATION + JANUS_LOG(LOG_WARN, "This version of libnice doesn't support setting the ICE nomination mode, ignoring '%s'\n", item->value); +#else + janus_ice_set_nomination_mode(item->value); +#endif + } + item = janus_config_get(config, config_nat, janus_config_type_item, "ice_keepalive_conncheck"); + if(item && item->value) + janus_ice_set_keepalive_conncheck_enabled(janus_is_true(item->value)); if(janus_ice_set_turn_server(turn_server, turn_port, turn_type, turn_user, turn_pwd) < 0) { if(!ignore_unreachable_ice_server) { JANUS_LOG(LOG_FATAL, "Invalid TURN address %s:%u\n", turn_server, turn_port); @@ -5016,7 +5032,7 @@ gint main(int argc, char *argv[]) if(item && item->value) enable_events = janus_is_true(item->value); if(!enable_events) { - JANUS_LOG(LOG_WARN, "Event handlers support disabled\n"); + JANUS_LOG(LOG_INFO, "Event handlers support disabled\n"); } else { gchar **disabled_eventhandlers = NULL; path = EVENTDIR; diff --git a/janus.ggo b/janus.ggo index b668b08ef3..fa8b157b6c 100644 --- a/janus.ggo +++ b/janus.ggo @@ -1,4 +1,4 @@ -#Janus 0.10.10 gengetopt file +#Janus 0.11.1 gengetopt file option "daemon" b "Launch Janus in background as a daemon" flag off option "pid-file" p "Open the specified PID file when starting Janus (default=none)" string typestr="path" optional option "disable-stdout" N "Disable stdout based logging" flag off diff --git a/mainpage.dox b/mainpage.dox index c405343047..b28bed6493 100644 --- a/mainpage.dox +++ b/mainpage.dox @@ -67,7 +67,7 @@ * * \section copyright Copyright and author * - * Janus WebRTC Server © 2014-2020 Meetecho (http://www.meetecho.com/) + * Janus WebRTC Server © 2014-2021 Meetecho (http://www.meetecho.com/) * * \author Lorenzo Miniero ( \ref CREDITS ) * @@ -885,7 +885,7 @@ echotest.createOffer({ // janus.js does not use 'import' to access to the functionality of webrtc-adapter, // instead it expects a global object called 'adapter' for that. // Let's make that object available. - new webpack.ProvidePlugin({ adapter: 'webrtc-adapter' }) + new webpack.ProvidePlugin({ adapter: ['webrtc-adapter', 'default'] }) ] } \endverbatim @@ -3719,7 +3719,7 @@ ldd janus | grep asan /*! \page CREDITS Credits * - * Janus WebRTC Server © 2014-2020 Meetecho (http://www.meetecho.com/) + * Janus WebRTC Server © 2014-2021 Meetecho (http://www.meetecho.com/) * * \b Author: * Lorenzo Miniero diff --git a/npm/janus.d.ts b/npm/janus.d.ts index bb6450e9cb..bbd6cf017d 100644 --- a/npm/janus.d.ts +++ b/npm/janus.d.ts @@ -5,9 +5,9 @@ declare namespace JanusJS { isArray: (array: any) => array is Array; extension: () => boolean; httpAPICall: (url: string, options: any) => void; - } - - interface DependenciesResult { + } + + interface DependenciesResult { adapter: any; newWebSocket: (server: string, protocol: string) => WebSocket; isArray: (array: any) => array is Array; @@ -116,9 +116,32 @@ declare namespace JanusJS { [otherProps: string]: any; }; jsep?: JSEP; + success?: Function; + error?: (error: any) => void; } interface PluginHandle { + plugin: string; + id: string; + token?: string; + detached : boolean; + webrtcStuff: { + started: boolean, + myStream: MediaStream, + streamExternal: boolean, + remoteStream: MediaStream, + mySdp: any, + mediaConstraints: any, + pc: RTCPeerConnection, + dataChannel: Array, + dtmfSender: any, + trickle: boolean, + iceDone: boolean, + volume: { + value: number, + timer: number + } + }; getId(): string; getPlugin(): string; send(message: PluginMessage): void; @@ -127,15 +150,20 @@ declare namespace JanusJS { handleRemoteJsep(params: { jsep: JSEP }): void; dtmf(params: any): void; data(params: any): void; + isAudioMuted(): boolean; + muteAudio(): void; + unmuteAudio(): void; isVideoMuted(): boolean; muteVideo(): void; unmuteVideo(): void; - getBitrate(): number; + getBitrate(): string; hangup(sendRequest?: boolean): void; detach(params: any): void; } class Janus { + static webRTCAdapter: any; + static safariVp8: boolean; static useDefaultDependencies(deps: Partial): DependenciesResult; static useOldDependencies(deps: Partial): DependenciesResult; static init(options: InitOptions): void; diff --git a/package.json b/package.json index 0ceb0c5216..136976b91f 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,6 @@ }, "homepage": "https://github.com/meetecho/janus-gateway#readme", "dependencies": { - "webrtc-adapter": "6.4.0" + "webrtc-adapter": "7.4.0" } } diff --git a/plugins/janus_audiobridge.c b/plugins/janus_audiobridge.c index dc15c7ed05..d922024266 100644 --- a/plugins/janus_audiobridge.c +++ b/plugins/janus_audiobridge.c @@ -5734,13 +5734,6 @@ static void *janus_audiobridge_handler(void *data) { } } else if(!strcasecmp(request_text, "changeroom")) { /* The participant wants to leave the current room and join another one without reconnecting (e.g., a sidebar) */ - janus_audiobridge_participant *participant = (janus_audiobridge_participant *)session->participant; - if(participant == NULL || participant->room == NULL) { - JANUS_LOG(LOG_ERR, "Can't change room (not in a room in the first place)\n"); - error_code = JANUS_AUDIOBRIDGE_ERROR_NOT_JOINED; - g_snprintf(error_cause, 512, "Can't change room (not in a room in the first place)"); - goto error; - } JANUS_VALIDATE_JSON_OBJECT(root, join_parameters, error_code, error_cause, TRUE, JANUS_AUDIOBRIDGE_ERROR_MISSING_ELEMENT, JANUS_AUDIOBRIDGE_ERROR_INVALID_ELEMENT); @@ -5768,6 +5761,14 @@ static void *janus_audiobridge_handler(void *data) { room_id_str = (char *)json_string_value(room); } janus_mutex_lock(&rooms_mutex); + janus_audiobridge_participant *participant = (janus_audiobridge_participant *)session->participant; + if(participant == NULL || participant->room == NULL) { + janus_mutex_unlock(&rooms_mutex); + JANUS_LOG(LOG_ERR, "Can't change room (not in a room in the first place)\n"); + error_code = JANUS_AUDIOBRIDGE_ERROR_NOT_JOINED; + g_snprintf(error_cause, 512, "Can't change room (not in a room in the first place)"); + goto error; + } /* Is this the same room we're in? */ if(participant->room && ((!string_ids && participant->room->room_id == room_id) || (string_ids && participant->room->room_id_str && !strcmp(participant->room->room_id_str, room_id_str)))) { diff --git a/plugins/janus_duktape.c b/plugins/janus_duktape.c index 9a5ba7813e..497a68f965 100644 --- a/plugins/janus_duktape.c +++ b/plugins/janus_duktape.c @@ -266,7 +266,7 @@ janus_plugin *create(void) { /* Useful stuff */ volatile gint duktape_initialized = 0, duktape_stopping = 0; -janus_callbacks *janus_core = NULL; +janus_callbacks *duktape_janus_core = NULL; static char *duktape_folder = NULL; /* Duktape stuff */ @@ -401,7 +401,7 @@ static void *janus_duktape_async_event_helper(void *data) { return NULL; if(asev->type == janus_duktape_async_event_type_pushevent) { /* Send the event */ - janus_core->push_event(asev->session->handle, &janus_duktape_plugin, asev->transaction, asev->event, asev->jsep); + duktape_janus_core->push_event(asev->session->handle, &janus_duktape_plugin, asev->transaction, asev->event, asev->jsep); } json_decref(asev->event); json_decref(asev->jsep); @@ -646,7 +646,7 @@ static duk_ret_t janus_duktape_method_pushevent(duk_context *ctx) { return 1; } /* No SDP, send the event now */ - int res = janus_core->push_event(session->handle, &janus_duktape_plugin, transaction, event, NULL); + int res = duktape_janus_core->push_event(session->handle, &janus_duktape_plugin, transaction, event, NULL); janus_refcount_decrease(&session->ref); json_decref(event); duk_push_int(ctx, res); @@ -669,7 +669,7 @@ static duk_ret_t janus_duktape_method_notifyevent(duk_context *ctx) { if(event_text == NULL) return duk_throw(ctx); /* Get the arguments from the provided context */ - if(!janus_core->events_is_enabled()) { + if(!duktape_janus_core->events_is_enabled()) { /* Event handlers are disabled in the core, ignoring */ duk_push_int(ctx, 0); return 1; @@ -688,7 +688,7 @@ static duk_ret_t janus_duktape_method_notifyevent(duk_context *ctx) { janus_refcount_increase(&session->ref); janus_mutex_unlock(&duktape_sessions_mutex); /* Notify the event */ - janus_core->notify_event(&janus_duktape_plugin, session ? session->handle : NULL, event); + duktape_janus_core->notify_event(&janus_duktape_plugin, session ? session->handle : NULL, event); if(session != NULL) janus_refcount_decrease(&session->ref); duk_push_int(ctx, 0); @@ -697,7 +697,7 @@ static duk_ret_t janus_duktape_method_notifyevent(duk_context *ctx) { static duk_ret_t janus_duktape_method_eventsisenabled(duk_context *ctx) { /* Return info on whether event handlers are enabled in the core or not */ - duk_push_int(ctx, janus_core->events_is_enabled()); + duk_push_int(ctx, duktape_janus_core->events_is_enabled()); return 1; } @@ -719,7 +719,7 @@ static duk_ret_t janus_duktape_method_closepc(duk_context *ctx) { janus_refcount_increase(&session->ref); janus_mutex_unlock(&duktape_sessions_mutex); /* Close the PeerConnection */ - janus_core->close_pc(session->handle); + duktape_janus_core->close_pc(session->handle); duk_push_int(ctx, 0); return 1; } @@ -742,7 +742,7 @@ static duk_ret_t janus_duktape_method_endsession(duk_context *ctx) { janus_refcount_increase(&session->ref); janus_mutex_unlock(&duktape_sessions_mutex); /* Close the plugin handle */ - janus_core->end_session(session->handle); + duktape_janus_core->end_session(session->handle); duk_push_int(ctx, 0); return 1; } @@ -936,7 +936,7 @@ static duk_ret_t janus_duktape_method_setbitrate(duk_context *ctx) { /* Send a REMB right away too, if the PeerConnection is up */ if(g_atomic_int_get(&session->started)) { /* No limit ~= 10000000 */ - janus_core->send_remb(session->handle, session->bitrate ? session->bitrate : 10000000); + duktape_janus_core->send_remb(session->handle, session->bitrate ? session->bitrate : 10000000); } /* Done */ janus_refcount_decrease(&session->ref); @@ -1055,7 +1055,7 @@ static duk_ret_t janus_duktape_method_sendpli(duk_context *ctx) { /* Send a PLI */ session->pli_latest = janus_get_monotonic_time(); JANUS_LOG(LOG_HUGE, "Sending PLI to session %"SCNu32"\n", session->id); - janus_core->send_pli(session->handle); + duktape_janus_core->send_pli(session->handle); /* Done */ janus_refcount_decrease(&session->ref); duk_push_int(ctx, 0); @@ -1104,7 +1104,7 @@ static duk_ret_t janus_duktape_method_relayrtp(duk_context *ctx) { /* Send the RTP packet */ janus_plugin_rtp rtp = { .video = is_video, .buffer = (char *)payload, .length = len }; janus_plugin_rtp_extensions_reset(&rtp.extensions); - janus_core->relay_rtp(session->handle, &rtp); + duktape_janus_core->relay_rtp(session->handle, &rtp); duk_push_int(ctx, 0); return 1; } @@ -1150,7 +1150,7 @@ static duk_ret_t janus_duktape_method_relayrtcp(duk_context *ctx) { janus_mutex_unlock(&duktape_sessions_mutex); /* Send the RTCP packet */ janus_plugin_rtcp rtcp = { .video = is_video, .buffer = (char *)payload, .length = len }; - janus_core->relay_rtcp(session->handle, &rtcp); + duktape_janus_core->relay_rtcp(session->handle, &rtcp); duk_push_int(ctx, 0); return 1; } @@ -1203,7 +1203,7 @@ static duk_ret_t janus_duktape_method_relaytextdata(duk_context *ctx) { .buffer = (char *)payload, .length = len }; - janus_core->relay_data(session->handle, &data); + duktape_janus_core->relay_data(session->handle, &data); janus_refcount_decrease(&session->ref); duk_push_int(ctx, 0); return 1; @@ -1256,7 +1256,7 @@ static duk_ret_t janus_duktape_method_relaybinarydata(duk_context *ctx) { .buffer = (char *)payload, .length = len }; - janus_core->relay_data(session->handle, &data); + duktape_janus_core->relay_data(session->handle, &data); janus_refcount_decrease(&session->ref); duk_push_int(ctx, 0); return 1; @@ -1364,7 +1364,7 @@ static duk_ret_t janus_duktape_method_startrecording(duk_context *ctx) { /* Also send a keyframe request */ session->pli_latest = janus_get_monotonic_time(); JANUS_LOG(LOG_HUGE, "Sending PLI to session %"SCNu32"\n", session->id); - janus_core->send_pli(session->handle); + duktape_janus_core->send_pli(session->handle); } if(drc) { session->drc = drc; @@ -1699,7 +1699,7 @@ int janus_duktape_init(janus_callbacks *callback, const char *config_path) { } /* This is the callback we'll need to invoke to contact the Janus core */ - janus_core = callback; + duktape_janus_core = callback; /* Init the JS script, in case it's needed */ duk_get_global_string(duktape_ctx, "init"); @@ -2445,7 +2445,7 @@ void janus_duktape_incoming_rtp(janus_plugin_session *handle, janus_plugin_rtp * gint64 now = janus_get_monotonic_time(); if((now-session->pli_latest) >= ((gint64)session->pli_freq*G_USEC_PER_SEC)) { session->pli_latest = now; - janus_core->send_pli(handle); + duktape_janus_core->send_pli(handle); } } } @@ -2488,7 +2488,7 @@ void janus_duktape_incoming_rtcp(janus_plugin_session *handle, janus_plugin_rtcp uint32_t bitrate = janus_rtcp_get_remb(buf, len); if(bitrate > 0) { /* No limit ~= 10000000 */ - janus_core->send_remb(handle, session->bitrate ? session->bitrate : 10000000); + duktape_janus_core->send_remb(handle, session->bitrate ? session->bitrate : 10000000); } /* If there's an incoming PLI, instead, relay it to the source of the media if any */ if(janus_rtcp_has_pli(buf, len)) { @@ -2497,7 +2497,7 @@ void janus_duktape_incoming_rtcp(janus_plugin_session *handle, janus_plugin_rtcp /* Send a PLI */ session->sender->pli_latest = janus_get_monotonic_time(); JANUS_LOG(LOG_HUGE, "Sending PLI to session %"SCNu32"\n", session->sender->id); - janus_core->send_pli(session->sender->handle); + duktape_janus_core->send_pli(session->sender->handle); janus_mutex_unlock_nodebug(&session->sender->recipients_mutex); } } @@ -2733,7 +2733,7 @@ static void janus_duktape_relay_rtp_packet(gpointer data, gpointer user_data) { if(session->sim_context.need_pli && sender->handle) { /* Send a PLI */ JANUS_LOG(LOG_VERB, "We need a PLI for the simulcast context\n"); - janus_core->send_pli(sender->handle); + duktape_janus_core->send_pli(sender->handle); } /* Do we need to drop this? */ if(!relay) @@ -2787,10 +2787,10 @@ static void janus_duktape_relay_rtp_packet(gpointer data, gpointer user_data) { session->sim_context.changed_substream); } /* Send the packet */ - if(janus_core != NULL) { + if(duktape_janus_core != NULL) { janus_plugin_rtp rtp = { .video = packet->is_video, .buffer = (char *)packet->data, .length = packet->length }; janus_plugin_rtp_extensions_reset(&rtp.extensions); - janus_core->relay_rtp(session->handle, &rtp); + duktape_janus_core->relay_rtp(session->handle, &rtp); } /* Restore the timestamp and sequence number to what the publisher set them to */ packet->data->timestamp = htonl(packet->timestamp); @@ -2803,10 +2803,10 @@ static void janus_duktape_relay_rtp_packet(gpointer data, gpointer user_data) { /* Fix sequence number and timestamp (publisher switching may be involved) */ janus_rtp_header_update(packet->data, &session->rtpctx, packet->is_video, 0); /* Send the packet */ - if(janus_core != NULL) { + if(duktape_janus_core != NULL) { janus_plugin_rtp rtp = { .video = packet->is_video, .buffer = (char *)packet->data, .length = packet->length }; janus_plugin_rtp_extensions_reset(&rtp.extensions); - janus_core->relay_rtp(session->handle, &rtp); + duktape_janus_core->relay_rtp(session->handle, &rtp); } /* Restore the timestamp and sequence number to what the publisher set them to */ packet->data->timestamp = htonl(packet->timestamp); @@ -2827,7 +2827,7 @@ static void janus_duktape_relay_data_packet(gpointer data, gpointer user_data) { !session->accept_data || !g_atomic_int_get(&session->dataready)) { return; } - if(janus_core != NULL) { + if(duktape_janus_core != NULL) { JANUS_LOG(LOG_VERB, "Forwarding %s DataChannel message (%d bytes) to session %"SCNu32"\n", packet->textdata ? "text" : "binary", packet->length, session->id); janus_plugin_data data = { @@ -2837,7 +2837,7 @@ static void janus_duktape_relay_data_packet(gpointer data, gpointer user_data) { .buffer = (char *)packet->data, .length = packet->length }; - janus_core->relay_data(session->handle, &data); + duktape_janus_core->relay_data(session->handle, &data); } return; } diff --git a/plugins/janus_duktape_data.h b/plugins/janus_duktape_data.h index e705df33ba..384b89ab3e 100644 --- a/plugins/janus_duktape_data.h +++ b/plugins/janus_duktape_data.h @@ -41,7 +41,7 @@ /* Core pointer and related flags */ extern volatile gint duktape_initialized, duktape_stopping; -extern janus_callbacks *janus_core; +extern janus_callbacks *duktape_janus_core; /* Duktape context: we define context and mutex as extern */ extern duk_context *duktape_ctx; diff --git a/plugins/janus_lua.c b/plugins/janus_lua.c index 452d271673..0366cb570e 100644 --- a/plugins/janus_lua.c +++ b/plugins/janus_lua.c @@ -267,7 +267,7 @@ janus_plugin *create(void) { /* Useful stuff */ volatile gint lua_initialized = 0, lua_stopping = 0; -janus_callbacks *janus_core = NULL; +janus_callbacks *lua_janus_core = NULL; /* Lua stuff */ lua_State *lua_state = NULL; @@ -401,7 +401,7 @@ static void *janus_lua_async_event_helper(void *data) { return NULL; if(asev->type == janus_lua_async_event_type_pushevent) { /* Send the event */ - janus_core->push_event(asev->session->handle, &janus_lua_plugin, asev->transaction, asev->event, asev->jsep); + lua_janus_core->push_event(asev->session->handle, &janus_lua_plugin, asev->transaction, asev->event, asev->jsep); } json_decref(asev->event); json_decref(asev->jsep); @@ -571,7 +571,7 @@ static int janus_lua_method_pushevent(lua_State *s) { return 1; } /* No SDP, send the event now */ - int res = janus_core->push_event(session->handle, &janus_lua_plugin, transaction, event, NULL); + int res = lua_janus_core->push_event(session->handle, &janus_lua_plugin, transaction, event, NULL); janus_refcount_decrease(&session->ref); json_decref(event); lua_pushnumber(s, res); @@ -586,7 +586,7 @@ static int janus_lua_method_notifyevent(lua_State *s) { lua_pushnumber(s, -1); return 1; } - if(!janus_core->events_is_enabled()) { + if(!lua_janus_core->events_is_enabled()) { /* Event handlers are disabled in the core, ignoring */ lua_pushnumber(s, 0); return 1; @@ -608,7 +608,7 @@ static int janus_lua_method_notifyevent(lua_State *s) { janus_refcount_increase(&session->ref); janus_mutex_unlock(&lua_sessions_mutex); /* Notify the event */ - janus_core->notify_event(&janus_lua_plugin, session ? session->handle : NULL, event); + lua_janus_core->notify_event(&janus_lua_plugin, session ? session->handle : NULL, event); if(session != NULL) janus_refcount_decrease(&session->ref); lua_pushnumber(s, 0); @@ -624,7 +624,7 @@ static int janus_lua_method_eventsisenabled(lua_State *s) { return 1; } /* Event handlers are disabled in the core, ignoring */ - lua_pushnumber(s, janus_core->events_is_enabled()); + lua_pushnumber(s, lua_janus_core->events_is_enabled()); return 1; } @@ -648,7 +648,7 @@ static int janus_lua_method_closepc(lua_State *s) { janus_refcount_increase(&session->ref); janus_mutex_unlock(&lua_sessions_mutex); /* Close the PeerConnection */ - janus_core->close_pc(session->handle); + lua_janus_core->close_pc(session->handle); lua_pushnumber(s, 0); return 1; } @@ -673,7 +673,7 @@ static int janus_lua_method_endsession(lua_State *s) { janus_refcount_increase(&session->ref); janus_mutex_unlock(&lua_sessions_mutex); /* Close the plugin handle */ - janus_core->end_session(session->handle); + lua_janus_core->end_session(session->handle); lua_pushnumber(s, 0); return 1; } @@ -845,7 +845,7 @@ static int janus_lua_method_setbitrate(lua_State *s) { /* Send a REMB right away too, if the PeerConnection is up */ if(g_atomic_int_get(&session->started)) { /* No limit ~= 10000000 */ - janus_core->send_remb(session->handle, session->bitrate ? session->bitrate : 10000000); + lua_janus_core->send_remb(session->handle, session->bitrate ? session->bitrate : 10000000); } /* Done */ janus_refcount_decrease(&session->ref); @@ -958,7 +958,7 @@ static int janus_lua_method_sendpli(lua_State *s) { /* Send a PLI */ session->pli_latest = janus_get_monotonic_time(); JANUS_LOG(LOG_HUGE, "Sending PLI to session %"SCNu32"\n", session->id); - janus_core->send_pli(session->handle); + lua_janus_core->send_pli(session->handle); /* Done */ janus_refcount_decrease(&session->ref); lua_pushnumber(s, 0); @@ -994,7 +994,7 @@ static int janus_lua_method_relayrtp(lua_State *s) { /* Send the RTP packet */ janus_plugin_rtp rtp = { .video = is_video, .buffer = (char *)payload, .length = len }; janus_plugin_rtp_extensions_reset(&rtp.extensions); - janus_core->relay_rtp(session->handle, &rtp); + lua_janus_core->relay_rtp(session->handle, &rtp); lua_pushnumber(s, 0); return 1; } @@ -1027,7 +1027,7 @@ static int janus_lua_method_relayrtcp(lua_State *s) { janus_mutex_unlock(&lua_sessions_mutex); /* Send the RTCP packet */ janus_plugin_rtcp rtcp = { .video = is_video, .buffer = (char *)payload, .length = len }; - janus_core->relay_rtcp(session->handle, &rtcp); + lua_janus_core->relay_rtcp(session->handle, &rtcp); lua_pushnumber(s, 0); return 1; } @@ -1073,7 +1073,7 @@ static int janus_lua_method_relaytextdata(lua_State *s) { .buffer = (char *)payload, .length = len }; - janus_core->relay_data(session->handle, &data); + lua_janus_core->relay_data(session->handle, &data); janus_refcount_decrease(&session->ref); lua_pushnumber(s, 0); return 1; @@ -1120,7 +1120,7 @@ static int janus_lua_method_relaybinarydata(lua_State *s) { .buffer = (char *)payload, .length = len }; - janus_core->relay_data(session->handle, &data); + lua_janus_core->relay_data(session->handle, &data); janus_refcount_decrease(&session->ref); lua_pushnumber(s, 0); return 1; @@ -1229,7 +1229,7 @@ static int janus_lua_method_startrecording(lua_State *s) { /* Also send a keyframe request */ session->pli_latest = janus_get_monotonic_time(); JANUS_LOG(LOG_HUGE, "Sending PLI to session %"SCNu32"\n", session->id); - janus_core->send_pli(session->handle); + lua_janus_core->send_pli(session->handle); } if(drc) { session->drc = drc; @@ -1521,7 +1521,7 @@ int janus_lua_init(janus_callbacks *callback, const char *config_path) { } /* This is the callback we'll need to invoke to contact the Janus core */ - janus_core = callback; + lua_janus_core = callback; /* Init the Lua script, in case it's needed */ lua_getglobal(lua_state, "init"); @@ -2140,7 +2140,7 @@ void janus_lua_incoming_rtp(janus_plugin_session *handle, janus_plugin_rtp *rtp_ if((now-session->pli_latest) >= ((gint64)session->pli_freq*G_USEC_PER_SEC)) { session->pli_latest = now; JANUS_LOG(LOG_HUGE, "Sending PLI to session %"SCNu32"\n", session->id); - janus_core->send_pli(handle); + lua_janus_core->send_pli(handle); } } } @@ -2177,7 +2177,7 @@ void janus_lua_incoming_rtcp(janus_plugin_session *handle, janus_plugin_rtcp *pa guint32 bitrate = janus_rtcp_get_remb(buf, len); if(bitrate > 0) { /* No limit ~= 10000000 */ - janus_core->send_remb(handle, session->bitrate ? session->bitrate : 10000000); + lua_janus_core->send_remb(handle, session->bitrate ? session->bitrate : 10000000); } /* If there's an incoming PLI, instead, relay it to the source of the media if any */ if(janus_rtcp_has_pli(buf, len)) { @@ -2186,7 +2186,7 @@ void janus_lua_incoming_rtcp(janus_plugin_session *handle, janus_plugin_rtcp *pa /* Send a PLI */ session->sender->pli_latest = janus_get_monotonic_time(); JANUS_LOG(LOG_HUGE, "Sending PLI to session %"SCNu32"\n", session->sender->id); - janus_core->send_pli(session->sender->handle); + lua_janus_core->send_pli(session->sender->handle); janus_mutex_unlock_nodebug(&session->sender->recipients_mutex); } } @@ -2398,7 +2398,7 @@ static void janus_lua_relay_rtp_packet(gpointer data, gpointer user_data) { if(session->sim_context.need_pli && sender->handle) { /* Send a PLI */ JANUS_LOG(LOG_VERB, "We need a PLI for the simulcast context\n"); - janus_core->send_pli(sender->handle); + lua_janus_core->send_pli(sender->handle); } /* Do we need to drop this? */ if(!relay) @@ -2440,10 +2440,10 @@ static void janus_lua_relay_rtp_packet(gpointer data, gpointer user_data) { session->sim_context.changed_substream); } /* Send the packet */ - if(janus_core != NULL) { + if(lua_janus_core != NULL) { janus_plugin_rtp rtp = { .video = packet->is_video, .buffer = (char *)packet->data, .length = packet->length }; janus_plugin_rtp_extensions_reset(&rtp.extensions); - janus_core->relay_rtp(session->handle, &rtp); + lua_janus_core->relay_rtp(session->handle, &rtp); } /* Restore the timestamp and sequence number to what the publisher set them to */ packet->data->timestamp = htonl(packet->timestamp); @@ -2456,10 +2456,10 @@ static void janus_lua_relay_rtp_packet(gpointer data, gpointer user_data) { /* Fix sequence number and timestamp (publisher switching may be involved) */ janus_rtp_header_update(packet->data, &session->rtpctx, packet->is_video, 0); /* Send the packet */ - if(janus_core != NULL) { + if(lua_janus_core != NULL) { janus_plugin_rtp rtp = { .video = packet->is_video, .buffer = (char *)packet->data, .length = packet->length }; janus_plugin_rtp_extensions_reset(&rtp.extensions); - janus_core->relay_rtp(session->handle, &rtp); + lua_janus_core->relay_rtp(session->handle, &rtp); } /* Restore the timestamp and sequence number to what the publisher set them to */ packet->data->timestamp = htonl(packet->timestamp); @@ -2480,7 +2480,7 @@ static void janus_lua_relay_data_packet(gpointer data, gpointer user_data) { !session->accept_data || !g_atomic_int_get(&session->dataready)) { return; } - if(janus_core != NULL) { + if(lua_janus_core != NULL) { JANUS_LOG(LOG_VERB, "Forwarding %s DataChannel message (%d bytes) to session %"SCNu32"\n", packet->textdata ? "text" : "binary", packet->length, session->id); janus_plugin_data data = { @@ -2490,7 +2490,7 @@ static void janus_lua_relay_data_packet(gpointer data, gpointer user_data) { .buffer = (char *)packet->data, .length = packet->length }; - janus_core->relay_data(session->handle, &data); + lua_janus_core->relay_data(session->handle, &data); } return; } diff --git a/plugins/janus_lua_data.h b/plugins/janus_lua_data.h index 2b49954546..38cb60a51f 100644 --- a/plugins/janus_lua_data.h +++ b/plugins/janus_lua_data.h @@ -41,7 +41,7 @@ /* Core pointer and related flags */ extern volatile gint lua_initialized, lua_stopping; -extern janus_callbacks *janus_core; +extern janus_callbacks *lua_janus_core; /* Lua state: we define state and mutex as extern */ extern lua_State *lua_state; diff --git a/plugins/janus_recordplay.c b/plugins/janus_recordplay.c index 45e4d8c83d..2427d7afe9 100644 --- a/plugins/janus_recordplay.c +++ b/plugins/janus_recordplay.c @@ -411,6 +411,8 @@ typedef struct janus_recordplay_recording { janus_videocodec vcodec; /* Codec used for video, if available */ char *vfmtp; /* Video fmtp, if any */ int video_pt; /* Payload types to use for audio when playing recordings */ + guint8 audiolevel_ext_id; /* Audio level extmap ID */ + guint8 videoorient_ext_id; /* Video orientation extmap ID */ char *drc_file; /* Data file name */ gboolean textdata; /* Whether data format is text */ char *offer; /* The SDP offer that will be sent to watchers */ @@ -507,7 +509,8 @@ void janus_recordplay_send_rtcp_feedback(janus_plugin_session *handle, int video #define VIDEO_PT 100 /* Helper method to check which codec was used in a specific recording (and if it's end-to-end encrypted) */ -static const char *janus_recordplay_parse_codec(const char *dir, const char *filename, char *fmtp, size_t fmtplen, gboolean *e2ee) { +static const char *janus_recordplay_parse_codec(const char *dir, const char *filename, char *fmtp, size_t fmtplen, + uint8_t *audiolevel_ext_id, uint8_t *videoorient_ext_id, gboolean *e2ee) { if(dir == NULL || filename == NULL) return NULL; if(e2ee) @@ -629,6 +632,23 @@ static const char *janus_recordplay_parse_codec(const char *dir, const char *fil fclose(file); return NULL; } + /* Any RTP extension we care about? */ + json_t *exts = json_object_get(info, "x"); + if(exts && !data) { + int extid = 0; + const char *key = NULL, *extmap = NULL; + json_t *value = NULL; + json_object_foreach(exts, key, value) { + if(key == NULL || value == NULL || !json_is_string(value)) + continue; + extid = atoi(key); + extmap = json_string_value(value); + if(!video && !strcasecmp(extmap, JANUS_RTP_EXTMAP_AUDIO_LEVEL) && audiolevel_ext_id != NULL) + *audiolevel_ext_id = extid; + else if(video && !strcasecmp(extmap, JANUS_RTP_EXTMAP_VIDEO_ORIENTATION) && videoorient_ext_id != NULL) + *videoorient_ext_id = extid; + } + } const char *c = json_string_value(codec); if(data) { const char *dtype = NULL; @@ -679,6 +699,9 @@ static int janus_recordplay_generate_offer(janus_recordplay_recording *rec) { offer_data = (rec->drc_file != NULL); char s_name[100]; g_snprintf(s_name, sizeof(s_name), "Recording %"SCNu64, rec->id); + guint8 mid_ext_id = 1; + while(mid_ext_id == rec->audiolevel_ext_id || mid_ext_id == rec->videoorient_ext_id) + mid_ext_id++; janus_sdp *offer = janus_sdp_generate_offer( s_name, "1.1.1.1", JANUS_SDP_OA_AUDIO, offer_audio, @@ -686,13 +709,15 @@ static int janus_recordplay_generate_offer(janus_recordplay_recording *rec) { JANUS_SDP_OA_AUDIO_PT, rec->audio_pt, JANUS_SDP_OA_AUDIO_FMTP, rec->afmtp, JANUS_SDP_OA_AUDIO_DIRECTION, JANUS_SDP_SENDONLY, - JANUS_SDP_OA_AUDIO_EXTENSION, JANUS_RTP_EXTMAP_MID, 1, + JANUS_SDP_OA_AUDIO_EXTENSION, JANUS_RTP_EXTMAP_MID, mid_ext_id, + JANUS_SDP_OA_AUDIO_EXTENSION, JANUS_RTP_EXTMAP_AUDIO_LEVEL, rec->audiolevel_ext_id, JANUS_SDP_OA_VIDEO, offer_video, JANUS_SDP_OA_VIDEO_CODEC, janus_videocodec_name(rec->vcodec), JANUS_SDP_OA_VIDEO_FMTP, rec->vfmtp, JANUS_SDP_OA_VIDEO_PT, rec->video_pt, JANUS_SDP_OA_VIDEO_DIRECTION, JANUS_SDP_SENDONLY, - JANUS_SDP_OA_VIDEO_EXTENSION, JANUS_RTP_EXTMAP_MID, 1, + JANUS_SDP_OA_VIDEO_EXTENSION, JANUS_RTP_EXTMAP_MID, mid_ext_id, + JANUS_SDP_OA_AUDIO_EXTENSION, JANUS_RTP_EXTMAP_VIDEO_ORIENTATION, rec->videoorient_ext_id, JANUS_SDP_OA_DATA, offer_data, JANUS_SDP_OA_DONE); g_free(rec->offer); @@ -1672,6 +1697,30 @@ static void *janus_recordplay_handler(void *data) { rec->audio_pt = 9; } rec->video_pt = VIDEO_PT; + /* Check if relevant extensions are negotiated */ + GList *temp = offer->m_lines; + while(temp) { + /* Which media are available? */ + janus_sdp_mline *m = (janus_sdp_mline *)temp->data; + if(m->type == JANUS_SDP_AUDIO || m->type == JANUS_SDP_VIDEO) { + /* Are the extmaps we care about there? */ + GList *ma = m->attributes; + while(ma) { + janus_sdp_attribute *a = (janus_sdp_attribute *)ma->data; + if(a->name && a->value) { + if(m->type == JANUS_SDP_AUDIO && strstr(a->value, JANUS_RTP_EXTMAP_AUDIO_LEVEL)) { + if(janus_string_to_uint8(a->value, &rec->audiolevel_ext_id) < 0) + JANUS_LOG(LOG_WARN, "Invalid audio-level extension ID: %s\n", a->value); + } else if(m->type == JANUS_SDP_VIDEO && strstr(a->value, JANUS_RTP_EXTMAP_VIDEO_ORIENTATION)) { + if(janus_string_to_uint8(a->value, &rec->videoorient_ext_id) < 0) + JANUS_LOG(LOG_WARN, "Invalid video-orientation extension ID: %s\n", a->value); + } + } + ma = ma->next; + } + } + temp = temp->next; + } /* Create a date string */ time_t t = time(NULL); struct tm *tmv = localtime(&t); @@ -1688,6 +1737,9 @@ static void *janus_recordplay_handler(void *data) { } rec->arc_file = g_strdup(filename); janus_recorder *rc = janus_recorder_create(recordings_path, janus_audiocodec_name(rec->acodec), rec->arc_file); + /* If the audio-level extension has been negotiated, mark it in the recording */ + if(rec->audiolevel_ext_id > 0) + janus_recorder_add_extmap(rc, rec->audiolevel_ext_id, JANUS_RTP_EXTMAP_AUDIO_LEVEL); /* If media is encrypted, mark it in the recording */ if(e2ee) janus_recorder_encrypted(rc); @@ -1703,6 +1755,9 @@ static void *janus_recordplay_handler(void *data) { rec->vrc_file = g_strdup(filename); janus_recorder *rc = janus_recorder_create_full(recordings_path, janus_videocodec_name(rec->vcodec), rec->vfmtp, rec->vrc_file); + /* If the video-orientation extension has been negotiated, mark it in the recording */ + if(rec->videoorient_ext_id > 0) + janus_recorder_add_extmap(rc, rec->videoorient_ext_id, JANUS_RTP_EXTMAP_VIDEO_ORIENTATION); /* If media is encrypted, mark it in the recording */ if(e2ee) janus_recorder_encrypted(rc); @@ -1745,8 +1800,9 @@ static void *janus_recordplay_handler(void *data) { JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_MID, JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_RID, JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_REPAIRED_RID, - JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_FRAME_MARKING, JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_TRANSPORT_WIDE_CC, + JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_AUDIO_LEVEL, + JANUS_SDP_OA_ACCEPT_EXTMAP, JANUS_RTP_EXTMAP_VIDEO_ORIENTATION, JANUS_SDP_OA_DONE); g_free(answer->s_name); char s_name[100]; @@ -2101,7 +2157,7 @@ void janus_recordplay_update_recordings_list(void) { char fmtp[256]; fmtp[0] = '\0'; rec->acodec = janus_audiocodec_from_name(janus_recordplay_parse_codec(recordings_path, - rec->arc_file, fmtp, sizeof(fmtp), &e2ee)); + rec->arc_file, fmtp, sizeof(fmtp), &rec->audiolevel_ext_id, NULL, &e2ee)); if(strlen(fmtp) > 0) rec->afmtp = g_strdup(fmtp); if(e2ee) @@ -2117,7 +2173,7 @@ void janus_recordplay_update_recordings_list(void) { char fmtp[256]; fmtp[0] = '\0'; rec->vcodec = janus_videocodec_from_name(janus_recordplay_parse_codec(recordings_path, - rec->vrc_file, fmtp, sizeof(fmtp), &e2ee)); + rec->vrc_file, fmtp, sizeof(fmtp), NULL, &rec->videoorient_ext_id, &e2ee)); if(strlen(fmtp) > 0) rec->vfmtp = g_strdup(fmtp); if(e2ee) @@ -2129,7 +2185,7 @@ void janus_recordplay_update_recordings_list(void) { if(ext != NULL) *ext = '\0'; const char *textcodec = janus_recordplay_parse_codec(recordings_path, - rec->drc_file, NULL, sizeof(NULL), NULL); + rec->drc_file, NULL, sizeof(NULL), NULL, NULL, NULL); rec->textdata = textcodec && (!strcasecmp(textcodec, "text")); } rec->audio_pt = AUDIO_PT; diff --git a/plugins/janus_sip.c b/plugins/janus_sip.c index 03d9be6807..473075c01c 100644 --- a/plugins/janus_sip.c +++ b/plugins/janus_sip.c @@ -378,11 +378,40 @@ * When a session has been established, there are different requests that * you can use to interact with the session. * + * First of all, you can put a call on-hold with the \c hold request. + * By default, this request will send a new INVITE to the peer with a + * \c sendonly direction for media, but in case you want to set a + * different direction (\c recvonly or \c inactive ) you can do that by + * passing a \c direction attribute as well: + * +\verbatim +{ + "request" : "hold", + "direction" : "" +} +\endverbatim + * + * No WebRTC renegotiation will be involved here on the holder side, as + * this will only trigger a re-INVITE on the SIP side. To remove the + * call from on-hold, just send a \c unhold request to the plugin, + * which requires no additional attributes: + * +\verbatim +{ + "request" : "hold", + "direction" : "" +} +\endverbatim + * + * and will restore the media direction that was set in the SDP before + * putting the call on-hold. + * * The \c message request allows you to send a SIP MESSAGE to the peer: * \verbatim { "request" : "message", + "content_type" : "" "content" : "" } \endverbatim @@ -397,6 +426,7 @@ "event" : "message", "sender" : "", "displayname" : "", + "content_type" : "", "content" : "", "headers" : "" } @@ -750,6 +780,9 @@ static struct janus_json_parameter transfer_parameters[] = { {"uri", JSON_STRING, JANUS_JSON_PARAM_REQUIRED}, {"call_id", JANUS_JSON_STRING, 0} }; +static struct janus_json_parameter hold_parameters[] = { + {"direction", JSON_STRING, 0} +}; static struct janus_json_parameter recording_parameters[] = { {"action", JSON_STRING, JANUS_JSON_PARAM_REQUIRED}, {"audio", JANUS_JSON_BOOL, 0}, @@ -767,6 +800,7 @@ static struct janus_json_parameter info_parameters[] = { {"content", JSON_STRING, JANUS_JSON_PARAM_REQUIRED} }; static struct janus_json_parameter sipmessage_parameters[] = { + {"content_type", JSON_STRING, 0}, {"content", JSON_STRING, JANUS_JSON_PARAM_REQUIRED} }; @@ -2527,8 +2561,15 @@ static void janus_sip_hangup_media_internal(janus_plugin_session *handle) { session->media.autoaccept_reinvites = TRUE; session->media.ready = FALSE; session->media.on_hold = FALSE; + + /* Send a BYE or respond with 480 */ + if(g_atomic_int_get(&session->established) || session->status == janus_sip_call_status_inviting) + nua_bye(session->stack->s_nh_i, TAG_END()); + else + nua_respond(session->stack->s_nh_i, 480, sip_status_phrase(480), TAG_END()); + janus_sip_call_update_status(session, janus_sip_call_status_closing); - nua_bye(session->stack->s_nh_i, TAG_END()); + /* Notify the operation */ json_t *event = json_object(); json_object_set_new(event, "sip", json_string("event")); @@ -3539,6 +3580,7 @@ static void *janus_sip_handler(void *data) { /* Send an ack back */ result = json_object(); json_object_set_new(result, "event", json_string("calling")); + json_object_set_new(result, "call_id", json_string(session->callid)); } else if(!strcasecmp(request_text, "accept")) { if(session->status != janus_sip_call_status_invited) { JANUS_LOG(LOG_ERR, "Wrong state (not invited? status=%s)\n", janus_sip_call_status_string(session->status)); @@ -3999,6 +4041,8 @@ static void *janus_sip_handler(void *data) { result = json_object(); json_object_set_new(result, "event", json_string("declining")); json_object_set_new(result, "code", json_integer(response_code)); + if(session->callid) + json_object_set_new(result, "call_id", json_string(session->callid)); } else if(!strcasecmp(request_text, "transfer")) { /* Transfer an existing call */ JANUS_VALIDATE_JSON_OBJECT(root, transfer_parameters, @@ -4095,23 +4139,48 @@ static void *janus_sip_handler(void *data) { } gboolean hold = !strcasecmp(request_text, "hold"); if(hold != session->media.on_hold) { - /* To put the call on-hold, we need to set the direction to recvonly: + /* To put the call on-hold, we need to change the media direction: * resuming it means resuming the direction we had before */ + janus_sdp_mdirection hold_dir = JANUS_SDP_SENDONLY; + if(hold) { + /* By default when holding we use recvonly, but the + * actual direction to set can be passed via API too */ + JANUS_VALIDATE_JSON_OBJECT(root, hold_parameters, + error_code, error_cause, TRUE, + JANUS_SIP_ERROR_MISSING_ELEMENT, JANUS_SIP_ERROR_INVALID_ELEMENT); + if(error_code != 0) + goto error; + json_t *hdir = json_object_get(root, "direction"); + if(hdir != NULL) { + const char *dir = json_string_value(hdir); + hold_dir = janus_sdp_parse_mdirection(dir); + if(hold_dir != JANUS_SDP_SENDONLY && hold_dir != JANUS_SDP_RECVONLY && + hold_dir != JANUS_SDP_INACTIVE) { + /* Invalid direction */ + JANUS_LOG(LOG_ERR, "Invalid direction (can only be sendonly, recvonly or inactive)\n"); + error_code = JANUS_SIP_ERROR_INVALID_ELEMENT; + g_snprintf(error_cause, 512, "Invalid direction (can only be sendonly, recvonly or inactive)"); + goto error; + } + } + } session->media.on_hold = hold; janus_sdp_mline *m = janus_sdp_mline_find(session->sdp, JANUS_SDP_AUDIO); if(m) { if(hold) { /* Take note of the original media direction */ session->media.pre_hold_audio_dir = m->direction; - /* Update the media direction */ - switch(m->direction) { - case JANUS_SDP_DEFAULT: - case JANUS_SDP_SENDRECV: - m->direction = JANUS_SDP_SENDONLY; - break; - default: - m->direction = JANUS_SDP_INACTIVE; - break; + if(m->direction != hold_dir) { + /* Update the media direction */ + switch(m->direction) { + case JANUS_SDP_DEFAULT: + case JANUS_SDP_SENDRECV: + m->direction = hold_dir; + break; + default: + m->direction = JANUS_SDP_INACTIVE; + break; + } } } else { m->direction = session->media.pre_hold_audio_dir; @@ -4122,15 +4191,17 @@ static void *janus_sip_handler(void *data) { if(hold) { /* Take note of the original media direction */ session->media.pre_hold_video_dir = m->direction; - /* Update the media direction */ - switch(m->direction) { - case JANUS_SDP_DEFAULT: - case JANUS_SDP_SENDRECV: - m->direction = JANUS_SDP_SENDONLY; - break; - default: - m->direction = JANUS_SDP_INACTIVE; - break; + if(m->direction != hold_dir) { + /* Update the media direction */ + switch(m->direction) { + case JANUS_SDP_DEFAULT: + case JANUS_SDP_SENDRECV: + m->direction = hold_dir; + break; + default: + m->direction = JANUS_SDP_INACTIVE; + break; + } } } else { m->direction = session->media.pre_hold_video_dir; @@ -4395,7 +4466,7 @@ static void *janus_sip_handler(void *data) { result = json_object(); json_object_set_new(result, "event", json_string("infosent")); } else if(!strcasecmp(request_text, "message")) { - /* Send a SIP MESSAGE request: we'll only need the content */ + /* Send a SIP MESSAGE request: we'll only need the content and optional payload type */ if(!(session->status == janus_sip_call_status_inviting || janus_sip_call_is_established(session))) { JANUS_LOG(LOG_ERR, "Wrong state (not established? status=%s)\n", janus_sip_call_status_string(session->status)); @@ -4418,9 +4489,15 @@ static void *janus_sip_handler(void *data) { janus_mutex_unlock(&session->mutex); goto error; } + + const char *content_type = "text/plain"; + json_t *content_type_text = json_object_get(root, "content_type"); + if(content_type_text && json_is_string(content_type_text)) + content_type = json_string_value(content_type_text); + const char *msg_content = json_string_value(json_object_get(root, "content")); nua_message(session->stack->s_nh_i, - SIPTAG_CONTENT_TYPE_STR("text/plain"), + SIPTAG_CONTENT_TYPE_STR(content_type), SIPTAG_PAYLOAD_STR(msg_content), TAG_END()); /* Notify the operation */ @@ -4683,8 +4760,8 @@ void janus_sip_sofia_callback(nua_event_t event, int status, char const *phrase, nua_respond(nh, 500, sip_status_phrase(500), TAG_END()); break; } - if(sip->sip_from == NULL || sip->sip_from->a_url == NULL || - sip->sip_to == NULL || sip->sip_to->a_url == NULL) { + if(sip->sip_from == NULL || sip->sip_from->a_url->url_user == NULL || + sip->sip_to == NULL || sip->sip_to->a_url->url_user == NULL) { JANUS_LOG(LOG_ERR, "\tInvalid request (missing From or To)\n"); nua_respond(nh, 400, sip_status_phrase(400), TAG_END()); break; @@ -4859,6 +4936,8 @@ void janus_sip_sofia_callback(nua_event_t event, int status, char const *phrase, json_t *calling = json_object(); json_object_set_new(calling, "event", json_string(reinvite ? "updatingcall" : "incomingcall")); json_object_set_new(calling, "username", json_string(session->callee)); + if(session->callid) + json_object_set_new(calling, "call_id", json_string(session->callid)); if(sip->sip_from->a_display) { json_object_set_new(calling, "displayname", json_string(sip->sip_from->a_display)); } @@ -5027,6 +5106,8 @@ void janus_sip_sofia_callback(nua_event_t event, int status, char const *phrase, json_t *headers = janus_sip_get_incoming_headers(sip, session); json_object_set_new(result, "headers", headers); } + if(session->callid) + json_object_set_new(info, "call_id", json_string(session->callid)); json_object_set_new(info, "result", result); int ret = gateway->push_event(session->handle, &janus_sip_plugin, session->transaction, info, NULL); JANUS_LOG(LOG_VERB, " >> Pushing event to peer: %d (%s)\n", ret, janus_get_api_error(ret)); @@ -5039,11 +5120,12 @@ void janus_sip_sofia_callback(nua_event_t event, int status, char const *phrase, case nua_i_message: { JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", session->account.username, nua_event_name(event), status, phrase ? phrase : "??"); /* We expect a payload */ - if(!sip->sip_payload || !sip->sip_payload->pl_data) { + if(!sip->sip_content_type || !sip->sip_content_type->c_type || !sip->sip_payload || !sip->sip_payload->pl_data) { nua_respond(nh, 488, sip_status_phrase(488), NUTAG_WITH_CURRENT(nua), TAG_END()); return; } + const char *content_type = sip->sip_content_type->c_type; char *payload = sip->sip_payload->pl_data; /* Notify the application */ json_t *message = json_object(); @@ -5061,6 +5143,9 @@ void janus_sip_sofia_callback(nua_event_t event, int status, char const *phrase, json_t *headers = janus_sip_get_incoming_headers(sip, session); json_object_set_new(result, "headers", headers); } + if(session->callid) + json_object_set_new(message, "call_id", json_string(session->callid)); + json_object_set_new(result, "content_type", json_string(content_type)); json_object_set_new(message, "result", result); int ret = gateway->push_event(session->handle, &janus_sip_plugin, session->transaction, message, NULL); JANUS_LOG(LOG_VERB, " >> Pushing event to peer: %d (%s)\n", ret, janus_get_api_error(ret)); diff --git a/plugins/janus_streaming.c b/plugins/janus_streaming.c index bc94808738..0eec99675c 100644 --- a/plugins/janus_streaming.c +++ b/plugins/janus_streaming.c @@ -8138,6 +8138,7 @@ static void *janus_streaming_relay_thread(void *data) { JANUS_LOG(LOG_HUGE, "[%s] Got audio RTCP feedback: SSRC %"SCNu32"\n", name, janus_rtcp_get_sender_ssrc(buffer, bytes)); /* Relay on all sessions */ + packet.is_rtp = FALSE; packet.is_video = FALSE; packet.data = (janus_rtp_header *)buffer; packet.length = bytes; @@ -8164,6 +8165,7 @@ static void *janus_streaming_relay_thread(void *data) { JANUS_LOG(LOG_HUGE, "[%s] Got video RTCP feedback: SSRC %"SCNu32"\n", name, janus_rtcp_get_sender_ssrc(buffer, bytes)); /* Relay on all sessions */ + packet.is_rtp = FALSE; packet.is_video = TRUE; packet.data = (janus_rtp_header *)buffer; packet.length = bytes; diff --git a/plugins/janus_videoroom.c b/plugins/janus_videoroom.c index bcfd77b831..d781f0fc8e 100644 --- a/plugins/janus_videoroom.c +++ b/plugins/janus_videoroom.c @@ -103,8 +103,9 @@ room-: { * (invalid JSON, invalid request) which will always result in a * synchronous error response even for asynchronous requests. * - * \c create , \c destroy , \c edit , \c exists, \c list, \c allowed, \c kick - * and \c listparticipants are synchronous requests, which means you'll + * \c create , \c destroy , \c edit , \c exists, \c list, \c allowed, + * \c kick , \c moderate , \c enable_recording , \c listparticipants + * and \c listforwarders are synchronous requests, which means you'll * get a response directly within the context of the transaction. * \c create allows you to create a new video room dynamically, as an * alternative to using the configuration file; \c edit allows you to @@ -325,6 +326,34 @@ room-: { { "videoroom" : "success", } +\endverbatim + * + * As an administrator, you can also forcibly mute/unmute any of the media + * streams sent by participants (i.e., audio, video and data streams), + * using the \c moderate requests. Notice that if the participant is self + * muted on a stream, and you unmute that stream with \c moderate, they + * will NOT be unmuted: you'll simply remove any moderation block + * that may have been enforced on the participant for that medium + * themselves. The \c moderate request has to be formatted as follows: + * +\verbatim +{ + "request" : "moderate", + "secret" : "", + "room" : , + "id" : , + "mute_audio" : , + "mute_video" : , + "mute_data" : , +} +\endverbatim + * + * A successful request will result in a \c success response: + * +\verbatim +{ + "videoroom" : "success", +} \endverbatim * * To get a list of the available rooms (excluded those configured or @@ -976,10 +1005,11 @@ room-: { * can start relaying media from the mountpoint the viewer subscribed to * to the viewer themselves. * - * Notice that the same exact steps we just went through (\c watch request, - * followed by JSEP offer by the plugin, followed by \c start request with - * JSEP answer by the viewer) is what you also use when renegotiations are - * needed, e.g., for the purpose of ICE restarts. + * Notice that, in case you want to force an ICE restart for an existing + * subscription, you'll need to use \c configure instead, and add a + * \c restart attribute set to \c true ; this will result in a new JSEP + * SDP offer originated by the plugin, which you'll have to follow with + * a \c start request (again including the JSEP answer by the viewer). * * As a subscriber, you can temporarily pause and resume the whole media delivery * with a \c pause and, again, \c start request (in this case without any JSEP @@ -1292,6 +1322,12 @@ static struct janus_json_parameter allowed_parameters[] = { static struct janus_json_parameter kick_parameters[] = { {"secret", JSON_STRING, 0} }; +static struct janus_json_parameter moderate_parameters[] = { + {"secret", JSON_STRING, 0}, + {"mute_audio", JANUS_JSON_BOOL, 0}, + {"mute_video", JANUS_JSON_BOOL, 0}, + {"mute_data", JANUS_JSON_BOOL, 0} +}; static struct janus_json_parameter join_parameters[] = { {"ptype", JSON_STRING, JANUS_JSON_PARAM_REQUIRED}, {"audio", JANUS_JSON_BOOL, 0}, @@ -1431,8 +1467,8 @@ typedef struct janus_videoroom { uint32_t bitrate; /* Global bitrate limit */ gboolean bitrate_cap; /* Whether the above limit is insormountable */ uint16_t fir_freq; /* Regular FIR frequency (0=disabled) */ - janus_audiocodec acodec[3]; /* Audio codec(s) to force on publishers */ - janus_videocodec vcodec[3]; /* Video codec(s) to force on publishers */ + janus_audiocodec acodec[5]; /* Audio codec(s) to force on publishers */ + janus_videocodec vcodec[5]; /* Video codec(s) to force on publishers */ char *vp9_profile; /* VP9 codec profile to prefer, if more are negotiated */ char *h264_profile; /* H.264 codec profile to prefer, if more are negotiated */ gboolean do_opusfec; /* Whether inband FEC must be negotiated (note: only available for Opus) */ @@ -1579,15 +1615,15 @@ typedef struct janus_videoroom_publisher { guint8 audio_level_extmap_id; /* Audio level extmap ID */ guint8 video_orient_extmap_id; /* Video orientation extmap ID */ guint8 playout_delay_extmap_id; /* Playout delay extmap ID */ - gboolean audio_active; - gboolean video_active; + gboolean audio_active, audio_muted; + gboolean video_active, video_muted; int audio_dBov_level; /* Value in dBov of the audio level (last value from extension) */ int audio_active_packets; /* Participant's number of audio packets to accumulate */ int audio_dBov_sum; /* Participant's accumulated dBov value for audio level*/ int user_audio_active_packets; /* Participant's audio_active_packets overwriting global room setting */ int user_audio_level_average; /* Participant's audio_level_average overwriting global room setting */ gboolean talking; /* Whether this participant is currently talking (uses audio levels extension) */ - gboolean data_active; + gboolean data_active, data_muted; gboolean firefox; /* We send Firefox users a different kind of FIR */ uint32_t bitrate; gint64 remb_startup;/* Incremental changes on REMB to reach the target at startup */ @@ -1800,6 +1836,14 @@ static void janus_videoroom_codecstr(janus_videoroom *videoroom, char *audio_cod g_strlcat(audio_codecs, split, str_len); g_strlcat(audio_codecs, janus_audiocodec_name(videoroom->acodec[2]), str_len); } + if (videoroom->acodec[3] != JANUS_AUDIOCODEC_NONE) { + g_strlcat(audio_codecs, split, str_len); + g_strlcat(audio_codecs, janus_audiocodec_name(videoroom->acodec[3]), str_len); + } + if (videoroom->acodec[4] != JANUS_AUDIOCODEC_NONE) { + g_strlcat(audio_codecs, split, str_len); + g_strlcat(audio_codecs, janus_audiocodec_name(videoroom->acodec[4]), str_len); + } } if (video_codecs) { video_codecs[0] = 0; @@ -1812,6 +1856,14 @@ static void janus_videoroom_codecstr(janus_videoroom *videoroom, char *audio_cod g_strlcat(video_codecs, split, str_len); g_strlcat(video_codecs, janus_videocodec_name(videoroom->vcodec[2]), str_len); } + if (videoroom->vcodec[3] != JANUS_VIDEOCODEC_NONE) { + g_strlcat(video_codecs, split, str_len); + g_strlcat(video_codecs, janus_videocodec_name(videoroom->vcodec[3]), str_len); + } + if (videoroom->vcodec[4] != JANUS_VIDEOCODEC_NONE) { + g_strlcat(video_codecs, split, str_len); + g_strlcat(video_codecs, janus_videocodec_name(videoroom->vcodec[4]), str_len); + } } } @@ -2230,6 +2282,8 @@ int janus_videoroom_init(janus_callbacks *callback, const char *config_path) { videoroom->acodec[0] = JANUS_AUDIOCODEC_OPUS; videoroom->acodec[1] = JANUS_AUDIOCODEC_NONE; videoroom->acodec[2] = JANUS_AUDIOCODEC_NONE; + videoroom->acodec[3] = JANUS_AUDIOCODEC_NONE; + videoroom->acodec[4] = JANUS_AUDIOCODEC_NONE; /* Check if we're forcing a different single codec, or allowing more than one */ if(audiocodec && audiocodec->value) { gchar **list = g_strsplit(audiocodec->value, ",", 4); @@ -2237,7 +2291,7 @@ int janus_videoroom_init(janus_callbacks *callback, const char *config_path) { if(codec != NULL) { int i=0; while(codec != NULL) { - if(i == 3) { + if(i == 5) { JANUS_LOG(LOG_WARN, "Ignoring extra audio codecs: %s\n", codec); break; } @@ -2253,6 +2307,8 @@ int janus_videoroom_init(janus_callbacks *callback, const char *config_path) { videoroom->vcodec[0] = JANUS_VIDEOCODEC_VP8; videoroom->vcodec[1] = JANUS_VIDEOCODEC_NONE; videoroom->vcodec[2] = JANUS_VIDEOCODEC_NONE; + videoroom->vcodec[3] = JANUS_VIDEOCODEC_NONE; + videoroom->vcodec[4] = JANUS_VIDEOCODEC_NONE; /* Check if we're forcing a different single codec, or allowing more than one */ if(videocodec && videocodec->value) { gchar **list = g_strsplit(videocodec->value, ",", 4); @@ -2260,7 +2316,7 @@ int janus_videoroom_init(janus_callbacks *callback, const char *config_path) { if(codec != NULL) { int i=0; while(codec != NULL) { - if(i == 3) { + if(i == 5) { JANUS_LOG(LOG_WARN, "Ignoring extra video codecs: %s\n", codec); break; } @@ -2274,19 +2330,25 @@ int janus_videoroom_init(janus_callbacks *callback, const char *config_path) { } if(vp9profile && vp9profile->value && (videoroom->vcodec[0] == JANUS_VIDEOCODEC_VP9 || videoroom->vcodec[1] == JANUS_VIDEOCODEC_VP9 || - videoroom->vcodec[2] == JANUS_VIDEOCODEC_VP9)) { + videoroom->vcodec[2] == JANUS_VIDEOCODEC_VP9 || + videoroom->vcodec[3] == JANUS_VIDEOCODEC_VP9 || + videoroom->vcodec[4] == JANUS_VIDEOCODEC_VP9)) { videoroom->vp9_profile = g_strdup(vp9profile->value); } if(h264profile && h264profile->value && (videoroom->vcodec[0] == JANUS_VIDEOCODEC_H264 || videoroom->vcodec[1] == JANUS_VIDEOCODEC_H264 || - videoroom->vcodec[2] == JANUS_VIDEOCODEC_H264)) { + videoroom->vcodec[2] == JANUS_VIDEOCODEC_H264 || + videoroom->vcodec[3] == JANUS_VIDEOCODEC_H264 || + videoroom->vcodec[4] == JANUS_VIDEOCODEC_H264)) { videoroom->h264_profile = g_strdup(h264profile->value); } if(fec && fec->value) { videoroom->do_opusfec = janus_is_true(fec->value); if(videoroom->acodec[0] != JANUS_AUDIOCODEC_OPUS && videoroom->acodec[1] != JANUS_AUDIOCODEC_OPUS && - videoroom->acodec[2] != JANUS_AUDIOCODEC_OPUS) { + videoroom->acodec[2] != JANUS_AUDIOCODEC_OPUS && + videoroom->acodec[3] != JANUS_AUDIOCODEC_OPUS && + videoroom->acodec[4] != JANUS_AUDIOCODEC_OPUS) { videoroom->do_opusfec = FALSE; JANUS_LOG(LOG_WARN, "Inband FEC is only supported for rooms that allow Opus: disabling it...\n"); } @@ -2294,7 +2356,9 @@ int janus_videoroom_init(janus_callbacks *callback, const char *config_path) { if(svc && svc->value && janus_is_true(svc->value)) { if(videoroom->vcodec[0] == JANUS_VIDEOCODEC_VP9 && videoroom->vcodec[1] == JANUS_VIDEOCODEC_NONE && - videoroom->vcodec[2] == JANUS_VIDEOCODEC_NONE) { + videoroom->vcodec[2] == JANUS_VIDEOCODEC_NONE && + videoroom->vcodec[3] == JANUS_VIDEOCODEC_NONE && + videoroom->vcodec[4] == JANUS_VIDEOCODEC_NONE) { videoroom->do_svc = TRUE; } else { JANUS_LOG(LOG_WARN, "SVC is only supported, in an experimental way, for VP9 only rooms: disabling it...\n"); @@ -2909,7 +2973,7 @@ static json_t *janus_videoroom_process_synchronous_request(janus_videoroom_sessi if(codec != NULL) { int i=0; while(codec != NULL) { - if(i == 3) { + if(i == 5) { break; } if(strlen(codec) == 0 || JANUS_AUDIOCODEC_NONE == janus_audiocodec_from_name(codec)) { @@ -2932,7 +2996,7 @@ static json_t *janus_videoroom_process_synchronous_request(janus_videoroom_sessi if(codec != NULL) { int i=0; while(codec != NULL) { - if(i == 3) { + if(i == 5) { break; } if(strlen(codec) == 0 || JANUS_VIDEOCODEC_NONE == janus_videocodec_from_name(codec)) { @@ -3077,6 +3141,8 @@ static json_t *janus_videoroom_process_synchronous_request(janus_videoroom_sessi videoroom->acodec[0] = JANUS_AUDIOCODEC_OPUS; videoroom->acodec[1] = JANUS_AUDIOCODEC_NONE; videoroom->acodec[2] = JANUS_AUDIOCODEC_NONE; + videoroom->acodec[3] = JANUS_AUDIOCODEC_NONE; + videoroom->acodec[4] = JANUS_AUDIOCODEC_NONE; /* Check if we're forcing a different single codec, or allowing more than one */ if(audiocodec) { const char *audiocodec_value = json_string_value(audiocodec); @@ -3085,7 +3151,7 @@ static json_t *janus_videoroom_process_synchronous_request(janus_videoroom_sessi if(codec != NULL) { int i=0; while(codec != NULL) { - if(i == 3) { + if(i == 5) { JANUS_LOG(LOG_WARN, "Ignoring extra audio codecs: %s\n", codec); break; } @@ -3101,6 +3167,8 @@ static json_t *janus_videoroom_process_synchronous_request(janus_videoroom_sessi videoroom->vcodec[0] = JANUS_VIDEOCODEC_VP8; videoroom->vcodec[1] = JANUS_VIDEOCODEC_NONE; videoroom->vcodec[2] = JANUS_VIDEOCODEC_NONE; + videoroom->vcodec[3] = JANUS_VIDEOCODEC_NONE; + videoroom->vcodec[4] = JANUS_VIDEOCODEC_NONE; /* Check if we're forcing a different single codec, or allowing more than one */ if(videocodec) { const char *videocodec_value = json_string_value(videocodec); @@ -3109,7 +3177,7 @@ static json_t *janus_videoroom_process_synchronous_request(janus_videoroom_sessi if(codec != NULL) { int i=0; while(codec != NULL) { - if(i == 3) { + if(i == 5) { JANUS_LOG(LOG_WARN, "Ignoring extra video codecs: %s\n", codec); break; } @@ -3124,20 +3192,26 @@ static json_t *janus_videoroom_process_synchronous_request(janus_videoroom_sessi const char *vp9_profile = json_string_value(vp9profile); if(vp9_profile && (videoroom->vcodec[0] == JANUS_VIDEOCODEC_VP9 || videoroom->vcodec[1] == JANUS_VIDEOCODEC_VP9 || - videoroom->vcodec[2] == JANUS_VIDEOCODEC_VP9)) { + videoroom->vcodec[2] == JANUS_VIDEOCODEC_VP9 || + videoroom->vcodec[3] == JANUS_VIDEOCODEC_VP9 || + videoroom->vcodec[4] == JANUS_VIDEOCODEC_VP9)) { videoroom->vp9_profile = g_strdup(vp9_profile); } const char *h264_profile = json_string_value(h264profile); if(h264_profile && (videoroom->vcodec[0] == JANUS_VIDEOCODEC_H264 || videoroom->vcodec[1] == JANUS_VIDEOCODEC_H264 || - videoroom->vcodec[2] == JANUS_VIDEOCODEC_H264)) { + videoroom->vcodec[2] == JANUS_VIDEOCODEC_H264 || + videoroom->vcodec[3] == JANUS_VIDEOCODEC_H264 || + videoroom->vcodec[4] == JANUS_VIDEOCODEC_H264)) { videoroom->h264_profile = g_strdup(h264_profile); } if(fec) { videoroom->do_opusfec = json_is_true(fec); if(videoroom->acodec[0] != JANUS_AUDIOCODEC_OPUS && videoroom->acodec[1] != JANUS_AUDIOCODEC_OPUS && - videoroom->acodec[2] != JANUS_AUDIOCODEC_OPUS) { + videoroom->acodec[2] != JANUS_AUDIOCODEC_OPUS && + videoroom->acodec[3] != JANUS_AUDIOCODEC_OPUS && + videoroom->acodec[4] != JANUS_AUDIOCODEC_OPUS) { videoroom->do_opusfec = FALSE; JANUS_LOG(LOG_WARN, "Inband FEC is only supported for rooms that allow Opus: disabling it...\n"); } @@ -3145,7 +3219,9 @@ static json_t *janus_videoroom_process_synchronous_request(janus_videoroom_sessi if(svc && json_is_true(svc)) { if(videoroom->vcodec[0] == JANUS_VIDEOCODEC_VP9 && videoroom->vcodec[1] == JANUS_VIDEOCODEC_NONE && - videoroom->vcodec[2] == JANUS_VIDEOCODEC_NONE) { + videoroom->vcodec[2] == JANUS_VIDEOCODEC_NONE && + videoroom->vcodec[3] == JANUS_VIDEOCODEC_NONE && + videoroom->vcodec[4] == JANUS_VIDEOCODEC_NONE) { videoroom->do_svc = TRUE; } else { JANUS_LOG(LOG_WARN, "SVC is only supported, in an experimental way, for VP9 only rooms: disabling it...\n"); @@ -3545,7 +3621,7 @@ static json_t *janus_videoroom_process_synchronous_request(janus_videoroom_sessi g_hash_table_iter_init(&iter, videoroom->participants); while (g_hash_table_iter_next(&iter, NULL, &value)) { janus_videoroom_publisher *p = value; - if(p && p->session) { + if(p && p->session && p->room) { g_clear_pointer(&p->room, janus_videoroom_room_dereference); /* Notify the user we're going to destroy the room... */ int ret = gateway->push_event(p->session->handle, &janus_videoroom_plugin, NULL, destroyed, NULL); @@ -4206,10 +4282,12 @@ static json_t *janus_videoroom_process_synchronous_request(janus_videoroom_sessi } janus_refcount_increase(&videoroom->ref); janus_mutex_unlock(&rooms_mutex); + janus_mutex_lock(&videoroom->mutex); /* A secret may be required for this action */ JANUS_CHECK_SECRET(videoroom->room_secret, root, "secret", error_code, error_cause, JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT, JANUS_VIDEOROOM_ERROR_UNAUTHORIZED); if(error_code != 0) { + janus_mutex_unlock(&videoroom->mutex); janus_refcount_decrease(&videoroom->ref); goto prepare_response; } @@ -4235,6 +4313,7 @@ static json_t *janus_videoroom_process_synchronous_request(janus_videoroom_sessi } } if(!ok) { + janus_mutex_unlock(&videoroom->mutex); JANUS_LOG(LOG_ERR, "Invalid element in the allowed array (not a string)\n"); error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT; g_snprintf(error_cause, 512, "Invalid element in the allowed array (not a string)"); @@ -4271,6 +4350,7 @@ static json_t *janus_videoroom_process_synchronous_request(janus_videoroom_sessi json_object_set_new(response, "allowed", list); } /* Done */ + janus_mutex_unlock(&videoroom->mutex); janus_refcount_decrease(&videoroom->ref); JANUS_LOG(LOG_VERB, "VideoRoom room allowed list updated\n"); goto prepare_response; @@ -4407,6 +4487,161 @@ static json_t *janus_videoroom_process_synchronous_request(janus_videoroom_sessi janus_refcount_decrease(&videoroom->ref); janus_refcount_decrease(&participant->ref); goto prepare_response; + } else if(!strcasecmp(request_text, "moderate")) { + JANUS_LOG(LOG_VERB, "Attempt to moderate a participant as a moderator in an existing VideoRoom room\n"); + if(!string_ids) { + JANUS_VALIDATE_JSON_OBJECT(root, room_parameters, + error_code, error_cause, TRUE, + JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT); + } else { + JANUS_VALIDATE_JSON_OBJECT(root, roomstr_parameters, + error_code, error_cause, TRUE, + JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT); + } + if(!string_ids) { + JANUS_VALIDATE_JSON_OBJECT(root, id_parameters, + error_code, error_cause, TRUE, + JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT); + } else { + JANUS_VALIDATE_JSON_OBJECT(root, idstr_parameters, + error_code, error_cause, TRUE, + JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT); + } + if(error_code != 0) + goto prepare_response; + JANUS_VALIDATE_JSON_OBJECT(root, moderate_parameters, + error_code, error_cause, TRUE, + JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT); + if(error_code != 0) + goto prepare_response; + json_t *room = json_object_get(root, "room"); + json_t *id = json_object_get(root, "id"); + guint64 room_id = 0; + char room_id_num[30], *room_id_str = NULL; + if(!string_ids) { + room_id = json_integer_value(room); + g_snprintf(room_id_num, sizeof(room_id_num), "%"SCNu64, room_id); + room_id_str = room_id_num; + } else { + room_id_str = (char *)json_string_value(room); + } + janus_mutex_lock(&rooms_mutex); + janus_videoroom *videoroom = NULL; + error_code = janus_videoroom_access_room(root, TRUE, FALSE, &videoroom, error_cause, sizeof(error_cause)); + if(error_code != 0) { + janus_mutex_unlock(&rooms_mutex); + goto prepare_response; + } + janus_refcount_increase(&videoroom->ref); + janus_mutex_unlock(&rooms_mutex); + janus_mutex_lock(&videoroom->mutex); + /* A secret may be required for this action */ + JANUS_CHECK_SECRET(videoroom->room_secret, root, "secret", error_code, error_cause, + JANUS_VIDEOROOM_ERROR_MISSING_ELEMENT, JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT, JANUS_VIDEOROOM_ERROR_UNAUTHORIZED); + if(error_code != 0) { + janus_mutex_unlock(&videoroom->mutex); + janus_refcount_decrease(&videoroom->ref); + goto prepare_response; + } + guint64 user_id = 0; + char user_id_num[30], *user_id_str = NULL; + if(!string_ids) { + user_id = json_integer_value(id); + g_snprintf(user_id_num, sizeof(user_id_num), "%"SCNu64, user_id); + user_id_str = user_id_num; + } else { + user_id_str = (char *)json_string_value(id); + } + janus_videoroom_publisher *participant = g_hash_table_lookup(videoroom->participants, + string_ids ? (gpointer)user_id_str : (gpointer)&user_id); + if(participant == NULL) { + janus_mutex_unlock(&videoroom->mutex); + janus_refcount_decrease(&videoroom->ref); + JANUS_LOG(LOG_ERR, "No such user %s in room %s\n", user_id_str, room_id_str); + error_code = JANUS_VIDEOROOM_ERROR_NO_SUCH_FEED; + g_snprintf(error_cause, 512, "No such user %s in room %s", user_id_str, room_id_str); + goto prepare_response; + } + janus_refcount_increase(&participant->ref); + /* Check if there's any media delivery to change */ + json_t *audio = json_object_get(root, "mute_audio"); + if(audio != NULL) { + gboolean audio_muted = json_is_true(audio); + if(participant->session && g_atomic_int_get(&participant->session->started) && + !audio_muted && participant->audio_active && participant->audio_muted) { + /* Video was just resumed, try resetting the RTP headers for viewers */ + janus_mutex_lock(&participant->subscribers_mutex); + GSList *ps = participant->subscribers; + while(ps) { + janus_videoroom_subscriber *l = (janus_videoroom_subscriber *)ps->data; + if(l) + l->context.v_seq_reset = TRUE; + ps = ps->next; + } + janus_mutex_unlock(&participant->subscribers_mutex); + } + participant->audio_muted = audio_muted; + } + json_t *video = json_object_get(root, "mute_video"); + if(video != NULL) { + gboolean video_muted = json_is_true(video); + if(participant->session && g_atomic_int_get(&participant->session->started) && + !video_muted && participant->video_active && participant->video_muted) { + /* Video was just resumed, try resetting the RTP headers for viewers */ + janus_mutex_lock(&participant->subscribers_mutex); + GSList *ps = participant->subscribers; + while(ps) { + janus_videoroom_subscriber *l = (janus_videoroom_subscriber *)ps->data; + if(l) + l->context.v_seq_reset = TRUE; + ps = ps->next; + } + janus_mutex_unlock(&participant->subscribers_mutex); + } + participant->video_muted = video_muted; + } + json_t *data = json_object_get(root, "mute_data"); + if(data != NULL) { + participant->data_muted = json_is_true(data); + } + /* If anything changed, prepare an event for this */ + if(audio || video || data) { + json_t *event = json_object(); + json_object_set_new(event, "videoroom", json_string("event")); + json_object_set_new(event, "room", string_ids ? json_string(participant->room_id_str) : json_integer(participant->room_id)); + json_object_set_new(event, "id", string_ids ? json_string(participant->user_id_str) : json_integer(participant->user_id)); + if(audio) + json_object_set_new(event, "audio-moderation", participant->audio_muted ? json_string("muted") : json_string("unmuted")); + if(video) + json_object_set_new(event, "video-moderation", participant->video_muted ? json_string("muted") : json_string("unmuted")); + if(data) + json_object_set_new(event, "data-moderation", participant->data_muted ? json_string("muted") : json_string("unmuted")); + /* Notify the speaker this event is related to as well */ + janus_videoroom_notify_participants(participant, event, TRUE); + json_decref(event); + /* Also notify event handlers */ + if(notify_events && gateway->events_is_enabled()) { + json_t *info = json_object(); + json_object_set_new(info, "videoroom", json_string("moderated")); + json_object_set_new(info, "room", string_ids ? json_string(videoroom->room_id_str) : json_integer(videoroom->room_id)); + json_object_set_new(info, "id", string_ids ? json_string(participant->user_id_str) : json_integer(participant->user_id)); + if(audio) + json_object_set_new(info, "audio", participant->audio_muted ? json_string("muted") : json_string("unmuted")); + if(video) + json_object_set_new(info, "video", participant->video_muted ? json_string("muted") : json_string("unmuted")); + if(data) + json_object_set_new(info, "data", participant->data_muted ? json_string("muted") : json_string("unmuted")); + gateway->notify_event(&janus_videoroom_plugin, NULL, info); + } + } + janus_mutex_unlock(&videoroom->mutex); + /* Prepare response */ + response = json_object(); + json_object_set_new(response, "videoroom", json_string("success")); + /* Done */ + janus_refcount_decrease(&videoroom->ref); + janus_refcount_decrease(&participant->ref); + goto prepare_response; } else if(!strcasecmp(request_text, "listparticipants")) { /* List all participants in a room, specifying whether they're publishers or just attendees */ if(!string_ids) { @@ -4894,7 +5129,7 @@ void janus_videoroom_incoming_rtp(janus_plugin_session *handle, janus_plugin_rtp char *buf = pkt->buffer; uint16_t len = pkt->length; /* In case this is an audio packet and we're doing talk detection, check the audio level extension */ - if(!video && videoroom->audiolevel_event && participant->audio_active) { + if(!video && videoroom->audiolevel_event && participant->audio_active && !participant->audio_muted) { int level = pkt->extensions.audio_level; if(level != -1) { participant->audio_dBov_sum += level; @@ -4944,7 +5179,7 @@ void janus_videoroom_incoming_rtp(janus_plugin_session *handle, janus_plugin_rtp } } - if((!video && participant->audio_active) || (video && participant->video_active)) { + if((!video && participant->audio_active && !participant->audio_muted) || (video && participant->video_active && !participant->video_muted)) { janus_rtp_header *rtp = (janus_rtp_header *)buf; int sc = video ? 0 : -1; /* Check if we're simulcasting, and if so, keep track of the "layer" */ @@ -5111,7 +5346,7 @@ void janus_videoroom_incoming_rtp(janus_plugin_session *handle, janus_plugin_rtp janus_mutex_unlock_nodebug(&participant->subscribers_mutex); /* Check if we need to send any REMB, FIR or PLI back to this publisher */ - if(video && participant->video_active) { + if(video && participant->video_active && !participant->video_muted) { /* Did we send a REMB already, or is it time to send one? */ gboolean send_remb = FALSE; if(participant->remb_latest == 0 && participant->remb_startup > 0) { @@ -5135,7 +5370,7 @@ void janus_videoroom_incoming_rtp(janus_plugin_session *handle, janus_plugin_rtp participant->remb_latest = janus_get_monotonic_time(); } /* Generate FIR/PLI too, if needed */ - if(video && participant->video_active && (videoroom->fir_freq > 0)) { + if(video && participant->video_active && !participant->video_muted && (videoroom->fir_freq > 0)) { /* We generate RTCP every tot seconds/frames */ gint64 now = janus_get_monotonic_time(); /* First check if this is a keyframe, though: if so, we reset the timer */ @@ -5215,7 +5450,7 @@ void janus_videoroom_incoming_data(janus_plugin_session *handle, janus_plugin_da janus_videoroom_publisher *participant = janus_videoroom_session_get_publisher_nodebug(session); if(participant == NULL) return; - if(g_atomic_int_get(&participant->destroyed) || !participant->data_active || participant->kicked) { + if(g_atomic_int_get(&participant->destroyed) || !participant->data_active || participant->data_muted || participant->kicked) { janus_videoroom_publisher_dereference_nodebug(participant); return; } @@ -5784,13 +6019,16 @@ static void *janus_videoroom_handler(void *data) { } /* Process the request */ json_t *audio = NULL, *video = NULL, *data = NULL, + *audiocodec = NULL, *videocodec = NULL, *bitrate = NULL, *record = NULL, *recfile = NULL, *user_audio_active_packets = NULL, *user_audio_level_average = NULL; if(!strcasecmp(request_text, "joinandconfigure")) { /* Also configure (or publish a new feed) audio/video/bitrate for this new publisher */ /* join_parameters were validated earlier. */ audio = json_object_get(root, "audio"); + audiocodec = json_object_get(root, "audiocodec"); video = json_object_get(root, "video"); + videocodec = json_object_get(root, "videocodec"); data = json_object_get(root, "data"); bitrate = json_object_get(root, "bitrate"); record = json_object_get(root, "record"); @@ -5860,11 +6098,51 @@ static void *janus_videoroom_handler(void *data) { JANUS_LOG(LOG_VERB, "Setting audio property: %s (room %s, user %s)\n", publisher->audio_active ? "true" : "false", publisher->room_id_str, publisher->user_id_str); } + if(audiocodec && json_string_value(json_object_get(msg->jsep, "sdp")) != NULL) { + /* The publisher would like to use an audio codec in particular */ + janus_audiocodec acodec = janus_audiocodec_from_name(json_string_value(audiocodec)); + if(acodec == JANUS_AUDIOCODEC_NONE || + (acodec != publisher->room->acodec[0] && + acodec != publisher->room->acodec[1] && + acodec != publisher->room->acodec[2] && + acodec != publisher->room->acodec[3] && + acodec != publisher->room->acodec[4])) { + JANUS_LOG(LOG_ERR, "Participant asked for audio codec '%s', but it's not allowed (room %s, user %s)\n", + json_string_value(audiocodec), publisher->room_id_str, publisher->user_id_str); + janus_refcount_decrease(&publisher->ref); + error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT; + g_snprintf(error_cause, 512, "Audio codec unavailable in this room"); + goto error; + } + publisher->acodec = acodec; + JANUS_LOG(LOG_VERB, "Participant asked for audio codec '%s' (room %s, user %s)\n", + json_string_value(audiocodec), publisher->room_id_str, publisher->user_id_str); + } if(video) { publisher->video_active = json_is_true(video); JANUS_LOG(LOG_VERB, "Setting video property: %s (room %s, user %s)\n", publisher->video_active ? "true" : "false", publisher->room_id_str, publisher->user_id_str); } + if(videocodec && json_string_value(json_object_get(msg->jsep, "sdp")) != NULL) { + /* The publisher would like to use a video codec in particular */ + janus_videocodec vcodec = janus_videocodec_from_name(json_string_value(videocodec)); + if(vcodec == JANUS_VIDEOCODEC_NONE || + (vcodec != publisher->room->vcodec[0] && + vcodec != publisher->room->vcodec[1] && + vcodec != publisher->room->vcodec[2] && + vcodec != publisher->room->vcodec[3] && + vcodec != publisher->room->vcodec[4])) { + JANUS_LOG(LOG_ERR, "Participant asked for video codec '%s', but it's not allowed (room %s, user %s)\n", + json_string_value(videocodec), publisher->room_id_str, publisher->user_id_str); + janus_refcount_decrease(&publisher->ref); + error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT; + g_snprintf(error_cause, 512, "Video codec unavailable in this room"); + goto error; + } + publisher->vcodec = vcodec; + JANUS_LOG(LOG_VERB, "Participant asked for video codec '%s' (room %s, user %s)\n", + json_string_value(videocodec), publisher->room_id_str, publisher->user_id_str); + } if(data) { publisher->data_active = json_is_true(data); JANUS_LOG(LOG_VERB, "Setting data property: %s (room %s, user %s)\n", @@ -5897,9 +6175,19 @@ static void *janus_videoroom_handler(void *data) { } /* Done */ janus_mutex_lock(&session->mutex); + /* Make sure the session has not been destroyed in the meanwhile */ + if(g_atomic_int_get(&session->destroyed)) { + janus_mutex_unlock(&session->mutex); + janus_mutex_unlock(&publisher->room->mutex); + janus_refcount_decrease(&publisher->room->ref); + janus_videoroom_publisher_destroy(publisher); + JANUS_LOG(LOG_ERR, "Session destroyed, invalidating new publisher\n"); + error_code = JANUS_VIDEOROOM_ERROR_UNKNOWN_ERROR; + g_snprintf(error_cause, 512, "Session destroyed, invalidating new publisher"); + goto error; + } session->participant_type = janus_videoroom_p_type_publisher; session->participant = publisher; - janus_mutex_unlock(&session->mutex); /* Return a list of all available publishers (those with an SDP available, that is) */ json_t *list = json_array(), *attendees = NULL; if(publisher->room->notify_joining) @@ -5910,6 +6198,7 @@ static void *janus_videoroom_handler(void *data) { g_hash_table_insert(publisher->room->participants, string_ids ? (gpointer)g_strdup(publisher->user_id_str) : (gpointer)janus_uint64_dup(publisher->user_id), publisher); + janus_mutex_unlock(&session->mutex); g_hash_table_iter_init(&iter, publisher->room->participants); while (!g_atomic_int_get(&publisher->room->destroyed) && g_hash_table_iter_next(&iter, NULL, &value)) { janus_videoroom_publisher *p = value; @@ -6299,7 +6588,7 @@ static void *janus_videoroom_handler(void *data) { json_t *user_audio_level_average = json_object_get(root, "audio_level_average"); if(audio) { gboolean audio_active = json_is_true(audio); - if(g_atomic_int_get(&session->started) && audio_active && !participant->audio_active) { + if(g_atomic_int_get(&session->started) && audio_active && !participant->audio_active && !participant->audio_muted) { /* Audio was just resumed, try resetting the RTP headers for viewers */ janus_mutex_lock(&participant->subscribers_mutex); GSList *ps = participant->subscribers; @@ -6321,7 +6610,9 @@ static void *janus_videoroom_handler(void *data) { if(acodec == JANUS_AUDIOCODEC_NONE || (acodec != participant->room->acodec[0] && acodec != participant->room->acodec[1] && - acodec != participant->room->acodec[2])) { + acodec != participant->room->acodec[2] && + acodec != participant->room->acodec[3] && + acodec != participant->room->acodec[4])) { JANUS_LOG(LOG_ERR, "Participant asked for audio codec '%s', but it's not allowed (room %s, user %s)\n", json_string_value(audiocodec), participant->room_id_str, participant->user_id_str); janus_refcount_decrease(&participant->ref); @@ -6335,7 +6626,7 @@ static void *janus_videoroom_handler(void *data) { } if(video) { gboolean video_active = json_is_true(video); - if(g_atomic_int_get(&session->started) && video_active && !participant->video_active) { + if(g_atomic_int_get(&session->started) && video_active && !participant->video_active && !participant->video_muted) { /* Video was just resumed, try resetting the RTP headers for viewers */ janus_mutex_lock(&participant->subscribers_mutex); GSList *ps = participant->subscribers; @@ -6357,7 +6648,9 @@ static void *janus_videoroom_handler(void *data) { if(vcodec == JANUS_VIDEOCODEC_NONE || (vcodec != participant->room->vcodec[0] && vcodec != participant->room->vcodec[1] && - vcodec != participant->room->vcodec[2])) { + vcodec != participant->room->vcodec[2] && + vcodec != participant->room->vcodec[3] && + vcodec != participant->room->vcodec[4])) { JANUS_LOG(LOG_ERR, "Participant asked for video codec '%s', but it's not allowed (room %s, user %s)\n", json_string_value(videocodec), participant->room_id_str, participant->user_id_str); janus_refcount_decrease(&participant->ref); @@ -7055,8 +7348,10 @@ static void *janus_videoroom_handler(void *data) { if(p != participant && p->sdp) count++; } + janus_refcount_increase(&videoroom->ref); janus_mutex_unlock(&videoroom->mutex); if(count == videoroom->max_publishers) { + janus_refcount_decrease(&videoroom->ref); participant->audio_active = FALSE; participant->video_active = FALSE; participant->data_active = FALSE; @@ -7066,6 +7361,7 @@ static void *janus_videoroom_handler(void *data) { goto error; } if(videoroom->require_e2ee && !e2ee && !participant->e2ee) { + janus_refcount_decrease(&videoroom->ref); participant->audio_active = FALSE; participant->video_active = FALSE; participant->data_active = FALSE; @@ -7082,6 +7378,7 @@ static void *janus_videoroom_handler(void *data) { char error_str[512]; janus_sdp *offer = janus_sdp_parse(msg_sdp, error_str, sizeof(error_str)); if(offer == NULL) { + janus_refcount_decrease(&videoroom->ref); json_decref(event); JANUS_LOG(LOG_ERR, "Error parsing offer: %s\n", error_str); error_code = JANUS_VIDEOROOM_ERROR_INVALID_SDP; @@ -7140,7 +7437,7 @@ static void *janus_videoroom_handler(void *data) { /* Check the codecs we can use, or the ones we should */ if(participant->acodec == JANUS_AUDIOCODEC_NONE) { int i=0; - for(i=0; i<3; i++) { + for(i=0; i<5; i++) { if(videoroom->acodec[i] == JANUS_AUDIOCODEC_NONE) continue; if(janus_sdp_get_codec_pt(offer, janus_audiocodec_name(videoroom->acodec[i])) != -1) { @@ -7159,7 +7456,7 @@ static void *janus_videoroom_handler(void *data) { char *h264_profile = videoroom->h264_profile; if(participant->vcodec == JANUS_VIDEOCODEC_NONE) { int i=0; - for(i=0; i<3; i++) { + for(i=0; i<5; i++) { if(videoroom->vcodec[i] == JANUS_VIDEOCODEC_NONE) continue; if(videoroom->vcodec[i] == JANUS_VIDEOCODEC_VP9 && vp9_profile) { @@ -7315,6 +7612,7 @@ static void *janus_videoroom_handler(void *data) { /* Is this room recorded, or are we recording this publisher already? */ janus_mutex_lock(&participant->rec_mutex); if(videoroom->record || participant->recording_active) { + participant->recording_active = TRUE; janus_videoroom_recorder_create(participant, participant->audio, participant->video, participant->data); } janus_mutex_unlock(&participant->rec_mutex); @@ -7392,6 +7690,7 @@ static void *janus_videoroom_handler(void *data) { janus_mutex_unlock(&participant->subscribers_mutex); json_decref(update); } + janus_refcount_decrease(&videoroom->ref); json_decref(event); json_decref(jsep); } diff --git a/postprocessing/janus-pp-rec.c b/postprocessing/janus-pp-rec.c index 4aa16939ab..b2c3688746 100644 --- a/postprocessing/janus-pp-rec.c +++ b/postprocessing/janus-pp-rec.c @@ -593,6 +593,44 @@ int main(int argc, char *argv[]) } /* Any codec-specific info? (just informational) */ const char *f = json_string_value(json_object_get(info, "f")); + /* Check if there are RTP extensions */ + json_t *exts = json_object_get(info, "x"); + if(exts != NULL) { + /* There are: check if audio-level and/or video-orientation + * are among them, as we might need them */ + int extid = 0; + const char *key = NULL, *extmap = NULL; + json_t *value = NULL; + json_object_foreach(exts, key, value) { + if(key == NULL || value == NULL || !json_is_string(value)) + continue; + extid = atoi(key); + extmap = json_string_value(value); + if(!strcasecmp(extmap, JANUS_PP_RTP_EXTMAP_AUDIO_LEVEL)) { + /* Audio level */ + if(audio_level_extmap_id != -1) { + if(audio_level_extmap_id != extid) { + JANUS_LOG(LOG_WARN, "Audio level extension ID found in header (%d) is different from the one provided via argument (%d)\n", + audio_level_extmap_id, extid); + } + } else { + audio_level_extmap_id = extid; + JANUS_LOG(LOG_INFO, "Audio level extension ID: %d\n", audio_level_extmap_id); + } + } else if(!strcasecmp(extmap, JANUS_PP_RTP_EXTMAP_VIDEO_ORIENTATION)) { + /* Video orientation */ + if(video_orient_extmap_id != -1) { + if(video_orient_extmap_id != extid) { + JANUS_LOG(LOG_WARN, "Video orientation extension ID found in header (%d) is different from the one provided via argument (%d)\n", + video_orient_extmap_id, extid); + } + } else { + video_orient_extmap_id = extid; + JANUS_LOG(LOG_INFO, "Video orientation extension ID: %d\n", video_orient_extmap_id); + } + } + } + } /* When was the file created? */ json_t *created = json_object_get(info, "s"); if(!created || !json_is_integer(created)) { @@ -639,7 +677,7 @@ int main(int argc, char *argv[]) } /* Now that we know what we're working with, check the extension */ - if(strcasecmp(extension, "opus") && strcasecmp(extension, "wav") && + if(extension && strcasecmp(extension, "opus") && strcasecmp(extension, "wav") && strcasecmp(extension, "webm") && strcasecmp(extension, "mp4") && strcasecmp(extension, "srt") && (!data || (data && textdata))) { /* Unsupported extension? */ @@ -778,7 +816,7 @@ int main(int argc, char *argv[]) rtp_read_n = (rtp->csrccount + rtp->extension)*4; bytes = fread(prebuffer+rtp_header_len, sizeof(char), rtp_read_n, file); if(bytes < rtp_read_n) { - JANUS_LOG(LOG_WARN, "Missing RTP packet header data (%d instead %"SCNu16")\n", + JANUS_LOG(LOG_WARN, "Missing RTP packet header data (%d instead %d)\n", rtp_header_len+bytes, rtp_header_len+rtp_read_n); break; } else { @@ -795,7 +833,7 @@ int main(int argc, char *argv[]) skip += 4 + rtp_read_n; bytes = fread(prebuffer+rtp_header_len, sizeof(char), rtp_read_n, file); if(bytes < rtp_read_n) { - JANUS_LOG(LOG_WARN, "Missing RTP packet header data (%d instead %"SCNu16")\n", + JANUS_LOG(LOG_WARN, "Missing RTP packet header data (%d instead %d)\n", rtp_header_len+bytes, rtp_header_len+rtp_read_n); break; } else { diff --git a/postprocessing/janus-pp-rec.ggo b/postprocessing/janus-pp-rec.ggo index 074d9d77be..4ebed2e397 100644 --- a/postprocessing/janus-pp-rec.ggo +++ b/postprocessing/janus-pp-rec.ggo @@ -1,4 +1,4 @@ -#Janus-pp-rec 0.10.10 gengetopt file +#Janus-pp-rec 0.11.1 gengetopt file usage "janus-pp-rec [OPTIONS] source.mjr [destination.[opus|wav|webm|mp4|srt]]" option "json" j "Only print JSON header" flag off option "header" H "Only parse .mjr header" flag off diff --git a/postprocessing/pcap2mjr.ggo b/postprocessing/pcap2mjr.ggo index 67abdc616c..cb39039016 100644 --- a/postprocessing/pcap2mjr.ggo +++ b/postprocessing/pcap2mjr.ggo @@ -1,4 +1,4 @@ -#pcap2mjr 0.10.10 gengetopt file +#pcap2mjr 0.11.1 gengetopt file usage "pcap2mjr [OPTIONS] source.pcap destination.mjr" option "codec" c "Codec the recording will contain (e.g., opus, vp8, etc.)" string typestr="codec" required option "ssrc" s "SSRC of the packets in the pcap file to save" int typestr="ssrc" required diff --git a/postprocessing/pp-av1.c b/postprocessing/pp-av1.c index c824ccba04..4b7493eb5b 100644 --- a/postprocessing/pp-av1.c +++ b/postprocessing/pp-av1.c @@ -96,12 +96,13 @@ int janus_pp_av1_create(char *destination, char *metadata, gboolean faststart) { #ifdef USE_CODECPAR #if LIBAVCODEC_VER_AT_LEAST(57, 25) AVCodec *codec = avcodec_find_encoder(AV_CODEC_ID_AV1); -#endif +#else if(!codec) { /* Error opening video codec */ JANUS_LOG(LOG_ERR, "Encoder not available\n"); return -1; } +#endif fctx->video_codec = codec; fctx->oformat->video_codec = codec->id; vStream = avformat_new_stream(fctx, codec); @@ -249,8 +250,8 @@ static void janus_pp_av1_parse_sh(char *buffer, uint16_t *width, uint16_t *heigh if(value) initial_display_delay = TRUE; /* Skip operating_points_cnt_minus_1 (5 bits) */ - value = janus_pp_av1_getbits(base, 5, &offset)+1; - for(i=0; i +#define JANUS_PP_RTP_EXTMAP_AUDIO_LEVEL "urn:ietf:params:rtp-hdrext:ssrc-audio-level" +#define JANUS_PP_RTP_EXTMAP_VIDEO_ORIENTATION "urn:3gpp:video-orientation" + typedef struct janus_pp_rtp_header { #if __BYTE_ORDER == __BIG_ENDIAN diff --git a/record.c b/record.c index 7c5eb7f7f3..d5117a8083 100644 --- a/record.c +++ b/record.c @@ -77,6 +77,8 @@ static void janus_recorder_free(const janus_refcount *recorder_ref) { recorder->codec = NULL; g_free(recorder->fmtp); recorder->fmtp = NULL; + if(recorder->extensions != NULL) + g_hash_table_destroy(recorder->extensions); g_free(recorder); } @@ -253,10 +255,21 @@ janus_recorder *janus_recorder_create_full(const char *dir, const char *codec, c return rc; } +int janus_recorder_add_extmap(janus_recorder *recorder, int id, const char *extmap) { + if(!recorder || g_atomic_int_get(&recorder->header) || id < 1 || id > 15 || extmap == NULL) + return -1; + janus_mutex_lock_nodebug(&recorder->mutex); + if(recorder->extensions == NULL) + recorder->extensions = g_hash_table_new_full(NULL, NULL, NULL, (GDestroyNotify)g_free); + g_hash_table_insert(recorder->extensions, GINT_TO_POINTER(id), g_strdup(extmap)); + janus_mutex_unlock_nodebug(&recorder->mutex); + return 0; +} + int janus_recorder_encrypted(janus_recorder *recorder) { if(!recorder) return -1; - if(!recorder->header) { + if(!g_atomic_int_get(&recorder->header)) { recorder->encrypted = TRUE; return 0; } @@ -295,6 +308,26 @@ int janus_recorder_save_frame(janus_recorder *recorder, char *buffer, uint lengt json_object_set_new(info, "c", json_string(recorder->codec)); /* Media codec */ if(recorder->fmtp) json_object_set_new(info, "f", json_string(recorder->fmtp)); /* Codec-specific info */ + if(recorder->extensions) { + /* Add the extmaps to the JSON object */ + json_t *extmaps = NULL; + GHashTableIter iter; + gpointer key, value; + g_hash_table_iter_init(&iter, recorder->extensions); + while(g_hash_table_iter_next(&iter, &key, &value)) { + int id = GPOINTER_TO_INT(key); + char *extmap = (char *)value; + if(id > 0 && id < 16 && extmap != NULL) { + if(extmaps == NULL) + extmaps = json_object(); + char id_str[3]; + g_snprintf(id_str, sizeof(id_str), "%d", id); + json_object_set_new(extmaps, id_str, json_string(extmap)); + } + } + if(extmaps != NULL) + json_object_set_new(info, "x", extmaps); + } json_object_set_new(info, "s", json_integer(recorder->created)); /* Created time */ json_object_set_new(info, "u", json_integer(janus_get_real_time())); /* First frame written time */ /* If media will be end-to-end encrypted, mark it in the recording header */ diff --git a/record.h b/record.h index 6a4337b2a3..482c9fc060 100644 --- a/record.h +++ b/record.h @@ -48,6 +48,8 @@ typedef struct janus_recorder { char *codec; /*! \brief Codec-specific info (e.g., H.264 or VP9 profile) */ char *fmtp; + /*! \brief List of RTP extensions (as a hashtable, indexed by ID) in this recording */ + GHashTable *extensions; /*! \brief When the recording file has been created and started */ gint64 created, started; /*! \brief Media this instance is recording */ @@ -90,6 +92,14 @@ janus_recorder *janus_recorder_create(const char *dir, const char *codec, const * @param[in] filename Filename to use for the recording * @returns A valid janus_recorder instance in case of success, NULL otherwise */ janus_recorder *janus_recorder_create_full(const char *dir, const char *codec, const char *fmtp, const char *filename); +/*! \brief Add an RTP extension to this recording + * \note This will only be possible BEFORE the first frame is written, as it needs to + * be reflected in the .mjr header: doing this after that will return an error. + * @param[in] recorder The janus_recorder instance to add the extension to + * @param[in] id Numeric ID of the RTP extension + * @param[in] extmap Namespace of the RTP extension + * @returns 0 in case of success, a negative integer otherwise */ +int janus_recorder_add_extmap(janus_recorder *recorder, int id, const char *extmap); /*! \brief Mark this recorder as end-to-end encrypted (e.g., via Insertable Streams) * \note This will only be possible BEFORE the first frame is written, as it needs to * be reflected in the .mjr header: doing this after that will return an error. Also diff --git a/rtcp.c b/rtcp.c index 4a3561ea4c..a266fed682 100644 --- a/rtcp.c +++ b/rtcp.c @@ -316,7 +316,7 @@ static void janus_rtcp_incoming_transport_cc(janus_rtcp_context *ctx, janus_rtcp } delta_us = delta*250; /* Print summary */ - JANUS_LOG(LOG_HUGE, " [%02"SCNu16"][%"SCNu16"] %s (%"SCNu32"us)\n", num, base_seq+num-1, + JANUS_LOG(LOG_HUGE, " [%02"SCNu16"][%"SCNu16"] %s (%"SCNu32"us)\n", num, (uint16_t)(base_seq+num-1), janus_rtp_packet_status_description(s), delta_us); iter = iter->next; } diff --git a/sctp.c b/sctp.c index 3ed4e0ae90..ec0ff77ad2 100644 --- a/sctp.c +++ b/sctp.c @@ -478,7 +478,7 @@ void janus_sctp_send_data(janus_sctp_association *sctp, char *label, char *proto int res = janus_sctp_send_text_or_binary(sctp, i, textdata, buf, len); if(res == -2) { /* Delivery failed with an EAGAIN, queue and retry later */ - JANUS_LOG(LOG_WARN, "[%"SCNu64"] Got EAGAIN when trying to send message on channel %"SCNu16", retrying later\n", + JANUS_LOG(LOG_WARN, "[%"SCNu64"] Got EAGAIN when trying to send message on channel %d, retrying later\n", sctp->handle_id, i); janus_sctp_pending_message *m = janus_sctp_pending_message_create(i, textdata, buf, len); if(sctp->pending_messages == NULL) diff --git a/sdp-utils.c b/sdp-utils.c index 80d646778e..8518bb3961 100644 --- a/sdp-utils.c +++ b/sdp-utils.c @@ -700,64 +700,20 @@ int janus_sdp_get_codec_pt_full(janus_sdp *sdp, const char *codec, const char *p ml = ml->next; continue; } - /* Look in all rtpmap attributes */ + /* Look in all rtpmap attributes first */ GList *ma = m->attributes; int pt = -1; - gboolean check_profile = FALSE; - gboolean got_profile = FALSE; + GList *pts = NULL; while(ma) { janus_sdp_attribute *a = (janus_sdp_attribute *)ma->data; - if(profile != NULL && a->name != NULL && a->value != NULL && !strcasecmp(a->name, "fmtp")) { - if(vp9) { - char profile_id[20]; - g_snprintf(profile_id, sizeof(profile_id), "profile-id=%s", profile); - if(strstr(a->value, profile_id) != NULL) { - /* Found */ - JANUS_LOG(LOG_VERB, "VP9 profile %s found --> %d\n", profile, pt); - if(check_profile) { - return pt; - } else { - got_profile = TRUE; - } - } - } else if(h264 && strstr(a->value, "packetization-mode=0") == NULL) { - /* We only support packetization-mode=1, no matter the profile */ - char profile_level_id[30]; - char *profile_lower = g_ascii_strdown(profile, -1); - g_snprintf(profile_level_id, sizeof(profile_level_id), "profile-level-id=%s", profile_lower); - g_free(profile_lower); - if(strstr(a->value, profile_level_id) != NULL) { - /* Found */ - JANUS_LOG(LOG_VERB, "H.264 profile %s found --> %d\n", profile, pt); - if(check_profile) { - return pt; - } else { - got_profile = TRUE; - } - } - /* Not found, try converting the profile to upper case */ - char *profile_upper = g_ascii_strup(profile, -1); - g_snprintf(profile_level_id, sizeof(profile_level_id), "profile-level-id=%s", profile_upper); - g_free(profile_upper); - if(strstr(a->value, profile_level_id) != NULL) { - /* Found */ - JANUS_LOG(LOG_VERB, "H.264 profile %s found --> %d\n", profile, pt); - if(check_profile) { - return pt; - } else { - got_profile = TRUE; - } - } - } - } else if(a->name != NULL && a->value != NULL && !strcasecmp(a->name, "rtpmap")) { + if(a->name != NULL && a->value != NULL && !strcasecmp(a->name, "rtpmap")) { pt = atoi(a->value); - check_profile = FALSE; if(pt < 0) { JANUS_LOG(LOG_ERR, "Invalid payload type (%s)\n", a->value); } else if(strstr(a->value, format) || strstr(a->value, format2)) { - if(profile != NULL && !got_profile && (vp9 || h264)) { - /* Let's check the profile first */ - check_profile = TRUE; + if(profile != NULL && (vp9 || h264)) { + /* Let's keep track of this payload type */ + pts = g_list_append(pts, GINT_TO_POINTER(pt)); } else { /* Payload type for codec found */ return pt; @@ -766,6 +722,54 @@ int janus_sdp_get_codec_pt_full(janus_sdp *sdp, const char *codec, const char *p } ma = ma->next; } + if(profile != NULL) { + /* Now look for the profile in the fmtp attributes */ + ma = m->attributes; + while(ma) { + janus_sdp_attribute *a = (janus_sdp_attribute *)ma->data; + if(profile != NULL && a->name != NULL && a->value != NULL && !strcasecmp(a->name, "fmtp")) { + /* Does this match the payload types we're looking for? */ + pt = atoi(a->value); + if(g_list_find(pts, GINT_TO_POINTER(pt)) == NULL) { + /* Not what we're looking for */ + ma = ma->next; + continue; + } + if(vp9) { + char profile_id[20]; + g_snprintf(profile_id, sizeof(profile_id), "profile-id=%s", profile); + if(strstr(a->value, profile_id) != NULL) { + /* Found */ + JANUS_LOG(LOG_VERB, "VP9 profile %s found --> %d\n", profile, pt); + return pt; + } + } else if(h264 && strstr(a->value, "packetization-mode=0") == NULL) { + /* We only support packetization-mode=1, no matter the profile */ + char profile_level_id[30]; + char *profile_lower = g_ascii_strdown(profile, -1); + g_snprintf(profile_level_id, sizeof(profile_level_id), "profile-level-id=%s", profile_lower); + g_free(profile_lower); + if(strstr(a->value, profile_level_id) != NULL) { + /* Found */ + JANUS_LOG(LOG_VERB, "H.264 profile %s found --> %d\n", profile, pt); + return pt; + } + /* Not found, try converting the profile to upper case */ + char *profile_upper = g_ascii_strup(profile, -1); + g_snprintf(profile_level_id, sizeof(profile_level_id), "profile-level-id=%s", profile_upper); + g_free(profile_upper); + if(strstr(a->value, profile_level_id) != NULL) { + /* Found */ + JANUS_LOG(LOG_VERB, "H.264 profile %s found --> %d\n", profile, pt); + return pt; + } + } + } + ma = ma->next; + } + } + if(pts != NULL) + g_list_free(pts); ml = ml->next; } return -1; diff --git a/sdp.c b/sdp.c index 8c6bd921b5..108b901abd 100644 --- a/sdp.c +++ b/sdp.c @@ -1090,6 +1090,7 @@ int janus_sdp_anonymize(janus_sdp *anon) { || !strcasecmp(a->name, "fingerprint") || !strcasecmp(a->name, "group") || !strcasecmp(a->name, "msid-semantic") + || !strcasecmp(a->name, "extmap-allow-mixed") || !strcasecmp(a->name, "rtcp-rsize")) { anon->attributes = g_list_remove(anon->attributes, a); temp = anon->attributes; diff --git a/transports/janus_http.c b/transports/janus_http.c index 6bee57ad37..e09f158253 100644 --- a/transports/janus_http.c +++ b/transports/janus_http.c @@ -773,7 +773,7 @@ int janus_http_init(janus_transport_callbacks *callback, const char *config_path /* Start with the Janus API web server now */ item = janus_config_get(config, config_general, janus_config_type_item, "http"); if(!item || !item->value || !janus_is_true(item->value)) { - JANUS_LOG(LOG_WARN, "HTTP webserver disabled\n"); + JANUS_LOG(LOG_VERB, "HTTP webserver disabled\n"); } else { uint16_t wsport = 8088; item = janus_config_get(config, config_general, janus_config_type_item, "port"); @@ -818,7 +818,7 @@ int janus_http_init(janus_transport_callbacks *callback, const char *config_path JANUS_LOG(LOG_VERB, "Using certificates:\n\t%s\n\t%s\n", server_pem, server_key); item = janus_config_get(config, config_general, janus_config_type_item, "https"); if(!item || !item->value || !janus_is_true(item->value)) { - JANUS_LOG(LOG_WARN, "HTTPS webserver disabled\n"); + JANUS_LOG(LOG_VERB, "HTTPS webserver disabled\n"); } else { if(!server_key || !server_pem) { JANUS_LOG(LOG_FATAL, "Missing certificate/key path\n"); @@ -849,7 +849,7 @@ int janus_http_init(janus_transport_callbacks *callback, const char *config_path /* Admin/monitor time: start web server, if enabled */ item = janus_config_get(config, config_admin, janus_config_type_item, "admin_http"); if(!item || !item->value || !janus_is_true(item->value)) { - JANUS_LOG(LOG_WARN, "Admin/monitor HTTP webserver disabled\n"); + JANUS_LOG(LOG_VERB, "Admin/monitor HTTP webserver disabled\n"); } else { uint16_t wsport = 7088; item = janus_config_get(config, config_admin, janus_config_type_item, "admin_port"); @@ -876,7 +876,7 @@ int janus_http_init(janus_transport_callbacks *callback, const char *config_path /* Do we also have to provide an HTTPS one? */ item = janus_config_get(config, config_admin, janus_config_type_item, "admin_https"); if(!item || !item->value || !janus_is_true(item->value)) { - JANUS_LOG(LOG_WARN, "Admin/monitor HTTPS webserver disabled\n"); + JANUS_LOG(LOG_VERB, "Admin/monitor HTTPS webserver disabled\n"); } else { if(!server_key) { JANUS_LOG(LOG_FATAL, "Missing certificate/key path\n"); @@ -1051,7 +1051,7 @@ int janus_http_send_message(janus_transport_session *transport, void *request_id transport = (janus_transport_session *)session->longpolls->data; msg = (janus_http_msg *)(transport ? transport->transport_p : NULL); /* Is this connection ready to send a response back? */ - if(msg && g_atomic_pointer_compare_and_exchange(&msg->longpoll, session, NULL)) { + if(msg && g_atomic_pointer_compare_and_exchange(&msg->longpoll, (volatile void *)session, NULL)) { janus_refcount_increase(&msg->ref); /* Send the events back */ if(g_atomic_int_compare_and_exchange(&msg->timeout_flag, 1, 0)) { @@ -1176,7 +1176,7 @@ void janus_http_session_claimed(janus_transport_session *transport, guint64 sess g_source_unref(msg->timeout); } msg->timeout = NULL; - if(g_atomic_pointer_compare_and_exchange(&msg->longpoll, session, NULL)) { + if(g_atomic_pointer_compare_and_exchange(&msg->longpoll, (volatile void *)session, NULL)) { /* Return an error on the long poll right away */ janus_http_timeout(transport, old_session); } @@ -1542,15 +1542,15 @@ static MHD_Result janus_http_handler(void *cls, struct MHD_Connection *connectio token_authorized = TRUE; } else { if(gateway->is_api_secret_valid(&janus_http_transport, secret)) { - /* API secret is valid */ + /* API secret is valid or disabled */ secret_authorized = TRUE; } if(gateway->is_auth_token_valid(&janus_http_transport, token)) { - /* Token is valid */ + /* Token is valid or disabled */ token_authorized = TRUE; } - /* We consider a request authorized if either the proper API secret or a valid token has been provided */ - if(!secret_authorized && !token_authorized) { + /* We consider a request authorized if both the token and the API secret are either disabled or valid */ + if(!secret_authorized || !token_authorized) { response = MHD_create_response_from_buffer(0, NULL, MHD_RESPMEM_PERSISTENT); janus_http_add_cors_headers(msg, response); ret = MHD_queue_response(connection, MHD_HTTP_FORBIDDEN, response); diff --git a/transports/janus_nanomsg.c b/transports/janus_nanomsg.c index 84197310bc..4c52d6d658 100644 --- a/transports/janus_nanomsg.c +++ b/transports/janus_nanomsg.c @@ -205,7 +205,7 @@ int janus_nanomsg_init(janus_transport_callbacks *callback, const char *config_p /* Setup the Janus API Nanomsg server(s) */ item = janus_config_get(config, config_general, janus_config_type_item, "enabled"); if(!item || !item->value || !janus_is_true(item->value)) { - JANUS_LOG(LOG_WARN, "Nanomsg server disabled (Janus API)\n"); + JANUS_LOG(LOG_VERB, "Nanomsg server disabled (Janus API)\n"); } else { item = janus_config_get(config, config_general, janus_config_type_item, "address"); const char *address = item && item->value ? item->value : NULL; @@ -246,7 +246,7 @@ int janus_nanomsg_init(janus_transport_callbacks *callback, const char *config_p /* Do the same for the Admin API, if enabled */ item = janus_config_get(config, config_admin, janus_config_type_item, "admin_enabled"); if(!item || !item->value || !janus_is_true(item->value)) { - JANUS_LOG(LOG_WARN, "Nanomsg server disabled (Admin API)\n"); + JANUS_LOG(LOG_VERB, "Nanomsg server disabled (Admin API)\n"); } else { item = janus_config_get(config, config_admin, janus_config_type_item, "admin_address"); const char *address = item && item->value ? item->value : NULL; diff --git a/transports/janus_pfunix.c b/transports/janus_pfunix.c index 898b7d1622..d100cabb14 100644 --- a/transports/janus_pfunix.c +++ b/transports/janus_pfunix.c @@ -162,7 +162,7 @@ static janus_mutex clients_mutex = JANUS_MUTEX_INITIALIZER; static void janus_pfunix_client_free(void *client_ref) { if(!client_ref) return; - JANUS_LOG(LOG_WARN, "Freeing unix sockets client\n"); + JANUS_LOG(LOG_INFO, "Freeing unix sockets client\n"); janus_pfunix_client *client = (janus_pfunix_client *) client_ref; if(client->messages != NULL) { char *response = NULL; @@ -282,7 +282,7 @@ int janus_pfunix_init(janus_transport_callbacks *callback, const char *config_pa /* Setup the Janus API Unix Sockets server(s) */ item = janus_config_get(config, config_general, janus_config_type_item, "enabled"); if(!item || !item->value || !janus_is_true(item->value)) { - JANUS_LOG(LOG_WARN, "Unix Sockets server disabled (Janus API)\n"); + JANUS_LOG(LOG_VERB, "Unix Sockets server disabled (Janus API)\n"); } else { item = janus_config_get(config, config_general, janus_config_type_item, "path"); char *pfname = (char *)(item && item->value ? item->value : NULL); @@ -313,7 +313,7 @@ int janus_pfunix_init(janus_transport_callbacks *callback, const char *config_pa /* Do the same for the Admin API, if enabled */ item = janus_config_get(config, config_admin, janus_config_type_item, "admin_enabled"); if(!item || !item->value || !janus_is_true(item->value)) { - JANUS_LOG(LOG_WARN, "Unix Sockets server disabled (Admin API)\n"); + JANUS_LOG(LOG_VERB, "Unix Sockets server disabled (Admin API)\n"); } else { item = janus_config_get(config, config_admin, janus_config_type_item, "admin_path"); char *pfname = (char *)(item && item->value ? item->value : NULL); diff --git a/transports/janus_rabbitmq.c b/transports/janus_rabbitmq.c index d8babf9d81..6d2ded6bec 100644 --- a/transports/janus_rabbitmq.c +++ b/transports/janus_rabbitmq.c @@ -139,10 +139,8 @@ typedef struct janus_rabbitmq_client { gboolean janus_api_enabled; /* Whether the Janus API via RabbitMQ is enabled */ amqp_bytes_t janus_exchange; /* AMQP exchange for outgoing messages */ amqp_bytes_t to_janus_queue; /* AMQP outgoing messages queue (Janus API) */ - amqp_bytes_t from_janus_queue; /* AMQP incoming messages queue (Janus API) */ gboolean admin_api_enabled; /* Whether the Janus API via RabbitMQ is enabled */ amqp_bytes_t to_janus_admin_queue; /* AMQP outgoing messages queue (Admin API) */ - amqp_bytes_t from_janus_admin_queue; /* AMQP incoming messages queue (Admin API) */ GThread *in_thread, *out_thread; /* Threads to handle incoming and outgoing queues */ GAsyncQueue *messages; /* Queue of outgoing messages to push */ janus_mutex mutex; /* Mutex to lock/unlock this session */ @@ -170,8 +168,8 @@ static janus_transport_session *rmq_session = NULL; /* Global properties */ static char *rmqhost = NULL, *vhost = NULL, *username = NULL, *password = NULL, *ssl_cacert_file = NULL, *ssl_cert_file = NULL, *ssl_key_file = NULL, - *to_janus = NULL, *from_janus = NULL, *to_janus_admin = NULL, *from_janus_admin = NULL, *janus_exchange = NULL, *janus_exchange_type = NULL; - + *to_janus = NULL, *from_janus = NULL, *to_janus_admin = NULL, *from_janus_admin = NULL, *janus_exchange = NULL, *janus_exchange_type = NULL, + *queue_name = NULL, *queue_name_admin = NULL; /* Transport implementation */ int janus_rabbitmq_init(janus_transport_callbacks *callback, const char *config_path) { @@ -302,34 +300,45 @@ int janus_rabbitmq_init(janus_transport_callbacks *callback, const char *config_ } } if(!item || !item->value || !janus_is_true(item->value)) { - JANUS_LOG(LOG_WARN, "RabbitMQ support disabled (Janus API)\n"); + JANUS_LOG(LOG_VERB, "RabbitMQ support disabled (Janus API)\n"); } else { - /* Parse configuration */ - item = janus_config_get(config, config_general, janus_config_type_item, "to_janus"); - if(!item || !item->value) { - JANUS_LOG(LOG_FATAL, "Missing name of incoming queue for RabbitMQ integration...\n"); - goto error; - } - to_janus = g_strdup(item->value); - item = janus_config_get(config, config_general, janus_config_type_item, "from_janus"); + + /* Get exchange name config, or set to default exchange */ + item = janus_config_get(config, config_general, janus_config_type_item, "janus_exchange"); if(!item || !item->value) { - JANUS_LOG(LOG_FATAL, "Missing name of outgoing queue for RabbitMQ integration...\n"); - goto error; + JANUS_LOG(LOG_INFO, "Missing name of outgoing exchange for RabbitMQ integration, using default\n"); + } else { + janus_exchange = g_strdup(item->value); } - from_janus = g_strdup(item->value); + + /* Get exchange type config, or set to default */ item = janus_config_get(config, config_general, janus_config_type_item, "janus_exchange_type"); if(!item || !item->value) { janus_exchange_type = (char *)JANUS_RABBITMQ_EXCHANGE_TYPE; } else { janus_exchange_type = g_strdup(item->value); } - item = janus_config_get(config, config_general, janus_config_type_item, "janus_exchange"); + + item = janus_config_get(config, config_general, janus_config_type_item, "queue_name"); + if(item && item->value) { + queue_name = g_strdup(item->value); + } + + item = janus_config_get(config, config_general, janus_config_type_item, "to_janus"); if(!item || !item->value) { - JANUS_LOG(LOG_INFO, "Missing name of outgoing exchange for RabbitMQ integration, using default\n"); - } else { - janus_exchange = g_strdup(item->value); + JANUS_LOG(LOG_FATAL, "Missing name of incoming queue/topic for RabbitMQ integration...\n"); + goto error; + } + to_janus = g_strdup(item->value); + + item = janus_config_get(config, config_general, janus_config_type_item, "from_janus"); + if(!item || !item->value) { + JANUS_LOG(LOG_FATAL, "Missing name of outgoing routing key for RabbitMQ integration...\n"); + goto error; } - if (janus_exchange == NULL) { + from_janus = g_strdup(item->value); + + if(janus_exchange == NULL) { JANUS_LOG(LOG_INFO, "RabbitMQ support for Janus API enabled, %s:%d (%s/%s) exchange_type:%s \n", rmqhost, rmqport, to_janus, from_janus, janus_exchange_type); } else { JANUS_LOG(LOG_INFO, "RabbitMQ support for Janus API enabled, %s:%d (%s/%s) exch: (%s) exchange_type:%s \n", rmqhost, rmqport, to_janus, from_janus, janus_exchange, janus_exchange_type); @@ -346,20 +355,27 @@ int janus_rabbitmq_init(janus_transport_callbacks *callback, const char *config_ } } if(!item || !item->value || !janus_is_true(item->value)) { - JANUS_LOG(LOG_WARN, "RabbitMQ support disabled (Admin API)\n"); + JANUS_LOG(LOG_VERB, "RabbitMQ support disabled (Admin API)\n"); } else { /* Parse configuration */ + item = janus_config_get(config, config_admin, janus_config_type_item, "queue_name_admin"); + if(item && item->value) { + queue_name_admin = g_strdup(item->value); + } + item = janus_config_get(config, config_admin, janus_config_type_item, "to_janus_admin"); if(!item || !item->value) { JANUS_LOG(LOG_FATAL, "Missing name of incoming queue for RabbitMQ integration...\n"); goto error; } to_janus_admin = g_strdup(item->value); + item = janus_config_get(config, config_admin, janus_config_type_item, "from_janus_admin"); if(!item || !item->value) { - JANUS_LOG(LOG_FATAL, "Missing name of outgoing queue for RabbitMQ integration...\n"); + JANUS_LOG(LOG_FATAL, "Missing name of outgoing routing key for RabbitMQ integration...\n"); goto error; } + from_janus_admin = g_strdup(item->value); JANUS_LOG(LOG_INFO, "RabbitMQ support for Admin API enabled, %s:%d (%s/%s)\n", rmqhost, rmqport, to_janus_admin, from_janus_admin); rmq_admin_api_enabled = TRUE; @@ -373,6 +389,7 @@ int janus_rabbitmq_init(janus_transport_callbacks *callback, const char *config_ /* Connect */ rmq_client->rmq_conn = amqp_new_connection(); amqp_socket_t *socket = NULL; + amqp_queue_declare_ok_t *declare = NULL; int status; JANUS_LOG(LOG_VERB, "Creating RabbitMQ socket...\n"); if (ssl_enabled) { @@ -446,22 +463,73 @@ int janus_rabbitmq_init(janus_transport_callbacks *callback, const char *config_ rmq_client->janus_api_enabled = FALSE; if(rmq_janus_api_enabled) { rmq_client->janus_api_enabled = TRUE; - JANUS_LOG(LOG_VERB, "Declaring incoming queue... (%s)\n", to_janus); - rmq_client->to_janus_queue = amqp_cstring_bytes(to_janus); - amqp_queue_declare(rmq_client->rmq_conn, rmq_client->rmq_channel, rmq_client->to_janus_queue, 0, 0, 0, 0, amqp_empty_table); - result = amqp_get_rpc_reply(rmq_client->rmq_conn); - if(result.reply_type != AMQP_RESPONSE_NORMAL) { - JANUS_LOG(LOG_FATAL, "Can't connect to RabbitMQ server: error declaring queue... %s, %s\n", amqp_error_string2(result.library_error), amqp_method_name(result.reply.id)); - goto error; + + /* Set queue options */ + amqp_boolean_t queue_durable = 0; + item = janus_config_get(config, config_general, janus_config_type_item, "queue_durable"); + if(item && item->value && janus_is_true(item->value)) { + queue_durable = 1; } - JANUS_LOG(LOG_VERB, "Declaring outgoing queue... (%s)\n", from_janus); - rmq_client->from_janus_queue = amqp_cstring_bytes(from_janus); - amqp_queue_declare(rmq_client->rmq_conn, rmq_client->rmq_channel, rmq_client->from_janus_queue, 0, 0, 0, 0, amqp_empty_table); - result = amqp_get_rpc_reply(rmq_client->rmq_conn); - if(result.reply_type != AMQP_RESPONSE_NORMAL) { - JANUS_LOG(LOG_FATAL, "Can't connect to RabbitMQ server: error declaring queue... %s, %s\n", amqp_error_string2(result.library_error), amqp_method_name(result.reply.id)); - goto error; + + amqp_boolean_t queue_exclusive = 0; + item = janus_config_get(config, config_general, janus_config_type_item, "queue_exclusive"); + if(item && item->value && janus_is_true(item->value)) { + queue_exclusive = 1; } + + amqp_boolean_t queue_autodelete = 0; + item = janus_config_get(config, config_general, janus_config_type_item, "queue_autodelete"); + if(item && item->value && janus_is_true(item->value)) { + queue_autodelete = 1; + } + + /* Case when we have a queue_name, and to_janus is the name of the topic to bind on (if exchange_type is topic) */ + if(queue_name != NULL) { + JANUS_LOG(LOG_VERB, "Declaring incoming queue (using queue_name)... (%s)\n", queue_name); + declare = amqp_queue_declare(rmq_client->rmq_conn, rmq_client->rmq_channel, amqp_cstring_bytes(queue_name), 0, queue_durable, queue_exclusive, queue_autodelete, amqp_empty_table); + rmq_client->to_janus_queue = declare->queue; + JANUS_LOG(LOG_VERB, "Incoming queue declared: (%s)\n", (char *) rmq_client->to_janus_queue.bytes); + result = amqp_get_rpc_reply(rmq_client->rmq_conn); + if(result.reply_type != AMQP_RESPONSE_NORMAL) { + JANUS_LOG(LOG_FATAL, "Can't connect to RabbitMQ server: error declaring queue... %s, %s\n", amqp_error_string2(result.library_error), amqp_method_name(result.reply.id)); + goto error; + } + + if(strcmp(janus_exchange_type, "topic") == 0 || strcmp(janus_exchange_type, "direct") == 0) { + JANUS_LOG(LOG_VERB, "Binding queue (%s) to routing key (%s)\n", queue_name, to_janus); + amqp_queue_bind(rmq_client->rmq_conn, rmq_client->rmq_channel, rmq_client->to_janus_queue, rmq_client->janus_exchange, amqp_cstring_bytes(to_janus), amqp_empty_table); + result = amqp_get_rpc_reply(rmq_client->rmq_conn); + if(result.reply_type != AMQP_RESPONSE_NORMAL) { + JANUS_LOG(LOG_FATAL, "Can't connect to RabbitMQ server: error binding queue... %s, %s\n", amqp_error_string2(result.library_error), amqp_method_name(result.reply.id)); + goto error; + } + } + + /* Case when to_janus is the name of the queue (and there's no binding) */ + } else { + JANUS_LOG(LOG_VERB, "Declaring incoming queue (using to_janus)... (%s)\n", to_janus); + declare = amqp_queue_declare(rmq_client->rmq_conn, rmq_client->rmq_channel, amqp_cstring_bytes(to_janus), 0, queue_durable, queue_exclusive, queue_autodelete, amqp_empty_table); + rmq_client->to_janus_queue = declare->queue; + JANUS_LOG(LOG_VERB, "Incoming queue declared: (%s)\n", (char *)rmq_client->to_janus_queue.bytes); + result = amqp_get_rpc_reply(rmq_client->rmq_conn); + if(result.reply_type != AMQP_RESPONSE_NORMAL) { + JANUS_LOG(LOG_FATAL, "Can't connect to RabbitMQ server: error declaring queue... %s, %s\n", amqp_error_string2(result.library_error), amqp_method_name(result.reply.id)); + goto error; + } + } + + /* By default, declare the outgoing queue */ + item = janus_config_get(config, config_general, janus_config_type_item, "declare_outgoing_queue"); + if(!item || !item->value || janus_is_true(item->value)) { + JANUS_LOG(LOG_VERB, "Declaring outgoing queue... (%s)\n", from_janus); + amqp_queue_declare(rmq_client->rmq_conn, rmq_client->rmq_channel, amqp_cstring_bytes(from_janus), 0, 0, 0, 0, amqp_empty_table); + result = amqp_get_rpc_reply(rmq_client->rmq_conn); + if(result.reply_type != AMQP_RESPONSE_NORMAL) { + JANUS_LOG(LOG_FATAL, "Can't connect to RabbitMQ server: error declaring queue... %s, %s\n", amqp_error_string2(result.library_error), amqp_method_name(result.reply.id)); + goto error; + } + } + amqp_basic_consume(rmq_client->rmq_conn, rmq_client->rmq_channel, rmq_client->to_janus_queue, amqp_empty_bytes, 0, 1, 0, amqp_empty_table); result = amqp_get_rpc_reply(rmq_client->rmq_conn); if(result.reply_type != AMQP_RESPONSE_NORMAL) { @@ -472,22 +540,74 @@ int janus_rabbitmq_init(janus_transport_callbacks *callback, const char *config_ rmq_client->admin_api_enabled = FALSE; if(rmq_admin_api_enabled) { rmq_client->admin_api_enabled = TRUE; - JANUS_LOG(LOG_VERB, "Declaring incoming queue... (%s)\n", to_janus_admin); - rmq_client->to_janus_admin_queue = amqp_cstring_bytes(to_janus_admin); - amqp_queue_declare(rmq_client->rmq_conn, rmq_client->rmq_channel, rmq_client->to_janus_admin_queue, 0, 0, 0, 0, amqp_empty_table); - result = amqp_get_rpc_reply(rmq_client->rmq_conn); - if(result.reply_type != AMQP_RESPONSE_NORMAL) { - JANUS_LOG(LOG_FATAL, "Can't connect to RabbitMQ server: error declaring queue... %s, %s\n", amqp_error_string2(result.library_error), amqp_method_name(result.reply.id)); - goto error; + + /* Set queue options */ + amqp_boolean_t queue_durable_admin = 0; + item = janus_config_get(config, config_admin, janus_config_type_item, "queue_durable_admin"); + if(item && item->value && janus_is_true(item->value)) { + queue_durable_admin = 1; } - JANUS_LOG(LOG_VERB, "Declaring outgoing queue... (%s)\n", from_janus_admin); - rmq_client->from_janus_admin_queue = amqp_cstring_bytes(from_janus_admin); - amqp_queue_declare(rmq_client->rmq_conn, rmq_client->rmq_channel, rmq_client->from_janus_admin_queue, 0, 0, 0, 0, amqp_empty_table); - result = amqp_get_rpc_reply(rmq_client->rmq_conn); - if(result.reply_type != AMQP_RESPONSE_NORMAL) { - JANUS_LOG(LOG_FATAL, "Can't connect to RabbitMQ server: error declaring queue... %s, %s\n", amqp_error_string2(result.library_error), amqp_method_name(result.reply.id)); - goto error; + + amqp_boolean_t queue_exclusive_admin = 0; + item = janus_config_get(config, config_admin, janus_config_type_item, "queue_exclusive_admin"); + if(item && item->value && janus_is_true(item->value)) { + queue_exclusive_admin = 1; + } + + amqp_boolean_t queue_autodelete_admin = 0; + item = janus_config_get(config, config_admin, janus_config_type_item, "queue_autodelete_admin"); + if(item && item->value && janus_is_true(item->value)) { + queue_autodelete_admin = 1; + } + + /* Case when we have a queue_name_admin, and to_janus_admin is the name of the routing key to bind on (if exchange_type is topic or direct) */ + if(queue_name_admin != NULL) { + JANUS_LOG(LOG_VERB, "Declaring incoming admin queue (using queue_name_admin)... (%s)\n", queue_name_admin); + declare = amqp_queue_declare(rmq_client->rmq_conn, rmq_client->rmq_channel, amqp_cstring_bytes(queue_name_admin), 0, queue_durable_admin, queue_exclusive_admin, queue_autodelete_admin, amqp_empty_table); + rmq_client->to_janus_admin_queue = declare->queue; + JANUS_LOG(LOG_VERB, "Incoming admin queue declared: (%s)\n", (char *) rmq_client->to_janus_queue.bytes); + result = amqp_get_rpc_reply(rmq_client->rmq_conn); + if(result.reply_type != AMQP_RESPONSE_NORMAL) { + JANUS_LOG(LOG_FATAL, "Can't connect to RabbitMQ server: error declaring queue... %s, %s\n", amqp_error_string2(result.library_error), amqp_method_name(result.reply.id)); + goto error; + } + + if(strcmp(janus_exchange_type, "topic") == 0 || strcmp(janus_exchange_type, "direct") == 0) { + JANUS_LOG(LOG_VERB, "Binding queue (%s) to routing key (%s)\n", queue_name_admin, to_janus_admin); + amqp_queue_bind(rmq_client->rmq_conn, rmq_client->rmq_channel, rmq_client->to_janus_admin_queue, rmq_client->janus_exchange, amqp_cstring_bytes(to_janus_admin), amqp_empty_table); + result = amqp_get_rpc_reply(rmq_client->rmq_conn); + if(result.reply_type != AMQP_RESPONSE_NORMAL) { + JANUS_LOG(LOG_FATAL, "Can't connect to RabbitMQ server: error binding queue... %s, %s\n", amqp_error_string2(result.library_error), amqp_method_name(result.reply.id)); + goto error; + } + } + + /* Case when to_janus_admin is the name of the queue (and there's no binding */ + } else { + JANUS_LOG(LOG_VERB, "Declaring incoming admin queue (using to_janus_admin)... (%s)\n", to_janus_admin); + rmq_client->to_janus_admin_queue = amqp_cstring_bytes(to_janus_admin); + declare = amqp_queue_declare(rmq_client->rmq_conn, rmq_client->rmq_channel, rmq_client->to_janus_admin_queue, 0, queue_durable_admin, queue_exclusive_admin, queue_autodelete_admin, amqp_empty_table); + rmq_client->to_janus_admin_queue = declare->queue; + JANUS_LOG(LOG_VERB, "Incoming admin queue declared: (%s)\n", (char *) rmq_client->to_janus_queue.bytes); + result = amqp_get_rpc_reply(rmq_client->rmq_conn); + if(result.reply_type != AMQP_RESPONSE_NORMAL) { + JANUS_LOG(LOG_FATAL, "Can't connect to RabbitMQ server: error declaring queue... %s, %s\n", amqp_error_string2(result.library_error), amqp_method_name(result.reply.id)); + goto error; + } } + + /* By default, declare the outgoing queue */ + item = janus_config_get(config, config_admin, janus_config_type_item, "declare_outgoing_queue_admin"); + if(!item || !item->value || janus_is_true(item->value)) { + JANUS_LOG(LOG_VERB, "Declaring outgoing queue... (%s)\n", from_janus_admin); + amqp_queue_declare(rmq_client->rmq_conn, rmq_client->rmq_channel, amqp_cstring_bytes(from_janus_admin), 0, 0, 0, 0, amqp_empty_table); + result = amqp_get_rpc_reply(rmq_client->rmq_conn); + if(result.reply_type != AMQP_RESPONSE_NORMAL) { + JANUS_LOG(LOG_FATAL, "Can't connect to RabbitMQ server: error declaring queue... %s, %s\n", amqp_error_string2(result.library_error), amqp_method_name(result.reply.id)); + goto error; + } + } + amqp_basic_consume(rmq_client->rmq_conn, rmq_client->rmq_channel, rmq_client->to_janus_admin_queue, amqp_empty_bytes, 0, 1, 0, amqp_empty_table); result = amqp_get_rpc_reply(rmq_client->rmq_conn); if(result.reply_type != AMQP_RESPONSE_NORMAL) { @@ -549,6 +669,9 @@ int janus_rabbitmq_init(janus_transport_callbacks *callback, const char *config_ g_free(username); g_free(password); g_free(janus_exchange); + g_free(janus_exchange_type); + g_free(queue_name); + g_free(queue_name_admin); g_free(to_janus); g_free(from_janus); g_free(to_janus_admin); @@ -587,6 +710,9 @@ void janus_rabbitmq_destroy(void) { g_free(username); g_free(password); g_free(janus_exchange); + g_free(janus_exchange_type); + g_free(queue_name); + g_free(queue_name_admin); g_free(to_janus); g_free(from_janus); g_free(to_janus_admin); @@ -782,17 +908,11 @@ void *janus_rmq_in_thread(void *data) { JANUS_LOG(LOG_VERB, "Delivery #%u, %.*s\n", (unsigned) d->delivery_tag, (int) d->routing_key.len, (char *) d->routing_key.bytes); /* Check if this is a Janus or Admin API request */ if(rmq_client->admin_api_enabled) { - if(d->routing_key.len == rmq_client->to_janus_admin_queue.len) { - size_t i=0; + char incoming_topic[d->routing_key.len + 2]; + /* Convert the amqp_bytes_t back to char* */ + g_strlcpy(incoming_topic, (char *)d->routing_key.bytes, d->routing_key.len + 1); + if(strcmp(incoming_topic, to_janus_admin) == 0) { admin = TRUE; - char *inq = (char *)d->routing_key.bytes; - char *expq = (char *)rmq_client->to_janus_admin_queue.bytes; - for(i=0; i< d->routing_key.len; i++) { - if(inq[i] != expq[i]) { - admin = FALSE; - break; - } - } } } JANUS_LOG(LOG_VERB, " -- This is %s API request\n", admin ? "an admin" : "a Janus"); @@ -857,7 +977,7 @@ void *janus_rmq_out_thread(void *data) { janus_mutex_lock(&rmq_client->mutex); /* Gotcha! Convert json_t to string */ char *payload_text = response->payload; - JANUS_LOG(LOG_VERB, "Sending %s API message to RabbitMQ (%zu bytes)...\n", response->admin ? "Admin" : "Janus", strlen(payload_text)); + JANUS_LOG(LOG_VERB, "Sending %s API message to RabbitMQ (%zu bytes) on exchange %s with routing key %s...\n", response->admin ? "Admin" : "Janus", strlen(payload_text), janus_exchange, response->admin ? from_janus_admin : from_janus); JANUS_LOG(LOG_VERB, "%s\n", payload_text); amqp_basic_properties_t props; props._flags = 0; @@ -871,7 +991,7 @@ void *janus_rmq_out_thread(void *data) { props.content_type = amqp_cstring_bytes("application/json"); amqp_bytes_t message = amqp_cstring_bytes(payload_text); int status = amqp_basic_publish(rmq_client->rmq_conn, rmq_client->rmq_channel, rmq_client->janus_exchange, - response->admin ? rmq_client->from_janus_admin_queue : rmq_client->from_janus_queue, + response->admin ? amqp_cstring_bytes(from_janus_admin) : amqp_cstring_bytes(from_janus), 0, 0, &props, message); if(status != AMQP_STATUS_OK) { JANUS_LOG(LOG_ERR, "Error publishing... %d, %s\n", status, amqp_error_string2(status)); diff --git a/transports/janus_websockets.c b/transports/janus_websockets.c index 49c4f4fa55..4d2b8e3486 100644 --- a/transports/janus_websockets.c +++ b/transports/janus_websockets.c @@ -602,7 +602,7 @@ int janus_websockets_init(janus_transport_callbacks *callback, const char *confi /* Setup the Janus API WebSockets server(s) */ item = janus_config_get(config, config_general, janus_config_type_item, "ws"); if(!item || !item->value || !janus_is_true(item->value)) { - JANUS_LOG(LOG_WARN, "WebSockets server disabled\n"); + JANUS_LOG(LOG_VERB, "WebSockets server disabled\n"); } else { uint16_t wsport = 8188; item = janus_config_get(config, config_general, janus_config_type_item, "ws_port"); @@ -636,7 +636,9 @@ int janus_websockets_init(janus_transport_callbacks *callback, const char *confi info.ssl_private_key_password = NULL; info.gid = -1; info.uid = -1; - info.options = 0; +#if (LWS_LIBRARY_VERSION_MAJOR == 3 && LWS_LIBRARY_VERSION_MINOR >= 2) || (LWS_LIBRARY_VERSION_MAJOR > 3) + info.options = LWS_SERVER_OPTION_FAIL_UPON_UNABLE_TO_BIND; +#endif /* Create the WebSocket context */ wss = lws_create_vhost(wsc, &info); if(wss == NULL) { @@ -648,7 +650,7 @@ int janus_websockets_init(janus_transport_callbacks *callback, const char *confi } item = janus_config_get(config, config_general, janus_config_type_item, "wss"); if(!item || !item->value || !janus_is_true(item->value)) { - JANUS_LOG(LOG_WARN, "Secure WebSockets server disabled\n"); + JANUS_LOG(LOG_VERB, "Secure WebSockets server disabled\n"); } else { uint16_t wsport = 8989; item = janus_config_get(config, config_general, janus_config_type_item, "wss_port"); @@ -701,10 +703,10 @@ int janus_websockets_init(janus_transport_callbacks *callback, const char *confi info.ssl_cipher_list = ciphers; info.gid = -1; info.uid = -1; -#if LWS_LIBRARY_VERSION_MAJOR >= 2 +#if (LWS_LIBRARY_VERSION_MAJOR == 3 && LWS_LIBRARY_VERSION_MINOR >= 2) || (LWS_LIBRARY_VERSION_MAJOR > 3) + info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT | LWS_SERVER_OPTION_FAIL_UPON_UNABLE_TO_BIND; +#elif LWS_LIBRARY_VERSION_MAJOR >= 2 info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; -#else - info.options = 0; #endif /* Create the secure WebSocket context */ swss = lws_create_vhost(wsc, &info); @@ -719,7 +721,7 @@ int janus_websockets_init(janus_transport_callbacks *callback, const char *confi /* Do the same for the Admin API, if enabled */ item = janus_config_get(config, config_admin, janus_config_type_item, "admin_ws"); if(!item || !item->value || !janus_is_true(item->value)) { - JANUS_LOG(LOG_WARN, "Admin WebSockets server disabled\n"); + JANUS_LOG(LOG_VERB, "Admin WebSockets server disabled\n"); } else { uint16_t wsport = 7188; item = janus_config_get(config, config_admin, janus_config_type_item, "admin_ws_port"); @@ -753,7 +755,9 @@ int janus_websockets_init(janus_transport_callbacks *callback, const char *confi info.ssl_private_key_password = NULL; info.gid = -1; info.uid = -1; - info.options = 0; +#if (LWS_LIBRARY_VERSION_MAJOR == 3 && LWS_LIBRARY_VERSION_MINOR >= 2) || (LWS_LIBRARY_VERSION_MAJOR > 3) + info.options = LWS_SERVER_OPTION_FAIL_UPON_UNABLE_TO_BIND; +#endif /* Create the WebSocket context */ admin_wss = lws_create_vhost(wsc, &info); if(admin_wss == NULL) { @@ -765,7 +769,7 @@ int janus_websockets_init(janus_transport_callbacks *callback, const char *confi } item = janus_config_get(config, config_admin, janus_config_type_item, "admin_wss"); if(!item || !item->value || !janus_is_true(item->value)) { - JANUS_LOG(LOG_WARN, "Secure Admin WebSockets server disabled\n"); + JANUS_LOG(LOG_VERB, "Secure Admin WebSockets server disabled\n"); } else { uint16_t wsport = 7989; item = janus_config_get(config, config_admin, janus_config_type_item, "admin_wss_port"); @@ -818,10 +822,10 @@ int janus_websockets_init(janus_transport_callbacks *callback, const char *confi info.ssl_cipher_list = ciphers; info.gid = -1; info.uid = -1; -#if LWS_LIBRARY_VERSION_MAJOR >= 2 +#if (LWS_LIBRARY_VERSION_MAJOR == 3 && LWS_LIBRARY_VERSION_MINOR >= 2) || (LWS_LIBRARY_VERSION_MAJOR > 3) + info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT | LWS_SERVER_OPTION_FAIL_UPON_UNABLE_TO_BIND; +#elif LWS_LIBRARY_VERSION_MAJOR >= 2 info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; -#else - info.options = 0; #endif /* Create the secure WebSocket context */ admin_swss = lws_create_vhost(wsc, &info);