Created
July 27, 2020 10:00
-
-
Save 648540858/44135642522fe4fdb6f15865581c4c9a to your computer and use it in GitHub Desktop.
janus.crash
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[Mon Jul 27 17:49:18 2020] [ERR] [janus.c:janus_process_incoming_request:1047] Invalid handle | |
[Mon Jul 27 17:49:18 2020] [6723535788159588] The DTLS handshake has been completed | |
[Mon Jul 27 17:49:18 2020] [janus.plugin.videocall-0x604000942250] WebRTC media is now available | |
[Mon Jul 27 17:49:18 2020] [janus.plugin.videocall-0x604000942250] Data channel available | |
[Mon Jul 27 17:49:18 2020] [WARN] [4597678052181907] Missing valid SRTP session (packet arrived too early?), skipping... | |
[Mon Jul 27 17:49:18 2020] [4597678052181907] The DTLS handshake has been completed | |
[Mon Jul 27 17:49:18 2020] [janus.plugin.videocall-0x604000942290] WebRTC media is now available | |
[Mon Jul 27 17:49:18 2020] [janus.plugin.videocall-0x604000942290] Data channel available | |
[Mon Jul 27 17:49:25 2020] [janus.plugin.videocall-0x604000942290] No WebRTC media anymore | |
[Mon Jul 27 17:49:25 2020] [4597678052181907] WebRTC resources freed; 0x612000159040 0x6070005fbdc0 | |
[Mon Jul 27 17:49:25 2020] Destroying session 707411221782073; 0x6070005fbdc0 | |
[Mon Jul 27 17:49:25 2020] Detaching handle from JANUS VideoCall plugin; 0x612000159040 0x604000942290 0x612000159040 0x614000146040 | |
[Mon Jul 27 17:49:25 2020] [ERR] [janus.c:janus_process_incoming_request:1062] Couldn't find any session 707411221782073... | |
[Mon Jul 27 17:49:25 2020] [WSS-0x617000926a00] Destroying WebSocket client | |
================================================================= | |
==12204==ERROR: AddressSanitizer: heap-use-after-free on address 0x6140001461e4 at pc 0x7facccb8c27c bp 0x7facc5945340 sp 0x7facc5945330 | |
READ of size 4 at 0x6140001461e4 thread T204 (hloop 672353578) | |
#0 0x7facccb8c27b in janus_videocall_incoming_rtp plugins/janus_videocall.c:760 | |
#1 0x558488af5f6f in janus_ice_cb_nice_recv /home/zc/janus/janus-gateway/ice.c:2584 | |
#2 0x7facdd4a3e92 in nice_component_emit_io_callback /home/zc/janus/libnice-0.1.16/agent/component.c:928 | |
#3 0x7facdd4ae7a3 in component_io_cb /home/zc/janus/libnice-0.1.16/agent/agent.c:5385 | |
#4 0x7facdd35bf5a (/lib/x86_64-linux-gnu/libgio-2.0.so.0+0xa1f5a) | |
#5 0x7facdd18678d in g_main_context_dispatch (/lib/x86_64-linux-gnu/libglib-2.0.so.0+0x5178d) | |
#6 0x7facdd186b3f (/lib/x86_64-linux-gnu/libglib-2.0.so.0+0x51b3f) | |
#7 0x7facdd186e32 in g_main_loop_run (/lib/x86_64-linux-gnu/libglib-2.0.so.0+0x51e32) | |
#8 0x558488ad9bfd in janus_ice_handle_thread /home/zc/janus/janus-gateway/ice.c:1165 | |
#9 0x7facdd1b0180 (/lib/x86_64-linux-gnu/libglib-2.0.so.0+0x7b180) | |
#10 0x7facdcc06668 in start_thread /build/glibc-5mDdLG/glibc-2.30/nptl/pthread_create.c:479 | |
#11 0x7facdcb2e2b2 in clone (/lib/x86_64-linux-gnu/libc.so.6+0x1222b2) | |
0x6140001461e4 is located 420 bytes inside of 440-byte region [0x614000146040,0x6140001461f8) | |
freed by thread T203 (hloop 459767805) here: | |
#0 0x7facdd7ff6ef in __interceptor_free (/lib/x86_64-linux-gnu/libasan.so.5+0x10d6ef) | |
#1 0x7facccb83155 in janus_videocall_session_free plugins/janus_videocall.c:417 | |
#2 0x7facccb833cd in janus_videocall_session_destroy plugins/janus_videocall.c:404 | |
#3 0x7facdd173ec3 (/lib/x86_64-linux-gnu/libglib-2.0.so.0+0x3eec3) | |
previously allocated by thread T9 here: | |
#0 0x7facdd7ffce6 in calloc (/lib/x86_64-linux-gnu/libasan.so.5+0x10dce6) | |
#1 0x7facdd18c5b0 in g_malloc0 (/lib/x86_64-linux-gnu/libglib-2.0.so.0+0x575b0) | |
#2 0x558488ad48ca in janus_ice_handle_attach_plugin /home/zc/janus/janus-gateway/ice.c:1227 | |
#3 0x558488b2469d in janus_process_incoming_request /home/zc/janus/janus-gateway/janus.c:1131 | |
#4 0x558488b38338 in janus_transport_requests /home/zc/janus/janus-gateway/janus.c:3202 | |
#5 0x7facdd1b0180 (/lib/x86_64-linux-gnu/libglib-2.0.so.0+0x7b180) | |
Thread T204 (hloop 672353578) created by T9 here: | |
#0 0x7facdd72c805 in pthread_create (/lib/x86_64-linux-gnu/libasan.so.5+0x3a805) | |
#1 0x7facdd1d2a16 (/lib/x86_64-linux-gnu/libglib-2.0.so.0+0x9da16) | |
Thread T9 created by T0 here: | |
#0 0x7facdd72c805 in pthread_create (/lib/x86_64-linux-gnu/libasan.so.5+0x3a805) | |
#1 0x7facdd1d2a16 (/lib/x86_64-linux-gnu/libglib-2.0.so.0+0x9da16) | |
Thread T203 (hloop 459767805) created by T9 here: | |
#0 0x7facdd72c805 in pthread_create (/lib/x86_64-linux-gnu/libasan.so.5+0x3a805) | |
#1 0x7facdd1d2a16 (/lib/x86_64-linux-gnu/libglib-2.0.so.0+0x9da16) | |
SUMMARY: AddressSanitizer: heap-use-after-free plugins/janus_videocall.c:760 in janus_videocall_incoming_rtp | |
Shadow bytes around the buggy address: | |
0x0c2880020be0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | |
0x0c2880020bf0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 fa | |
0x0c2880020c00: fa fa fa fa fa fa fa fa fd fd fd fd fd fd fd fd | |
0x0c2880020c10: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd | |
0x0c2880020c20: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd | |
=>0x0c2880020c30: fd fd fd fd fd fd fd fd fd fd fd fd[fd]fd fd fa | |
0x0c2880020c40: fa fa fa fa fa fa fa fa 00 00 00 00 00 00 00 00 | |
0x0c2880020c50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | |
0x0c2880020c60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | |
0x0c2880020c70: 00 00 00 00 00 00 00 00 00 00 fa fa fa fa fa fa | |
0x0c2880020c80: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa | |
Shadow byte legend (one shadow byte represents 8 application bytes): | |
Addressable: 00 | |
Partially addressable: 01 02 03 04 05 06 07 | |
Heap left redzone: fa | |
Freed heap region: fd | |
Stack left redzone: f1 | |
Stack mid redzone: f2 | |
Stack right redzone: f3 | |
Stack after return: f5 | |
Stack use after scope: f8 | |
Global redzone: f9 | |
Global init order: f6 | |
Poisoned by user: f7 | |
Container overflow: fc | |
Array cookie: ac | |
Intra object redzone: bb | |
ASan internal: fe | |
Left alloca redzone: ca | |
Right alloca redzone: cb | |
Shadow gap: cc | |
==12204==ABORTING | |
ps:I added the field displayName. Here is the modified file | |
/*! \file janus_videocall.c | |
* \author Lorenzo Miniero <lorenzo@meetecho.com> | |
* \copyright GNU General Public License v3 | |
* \brief Janus VideoCall plugin | |
* \details Check the \ref videocall for more details. | |
* | |
* \ingroup plugins | |
* \ref plugins | |
* | |
* \page videocall VideoCall plugin documentation | |
* This is a simple video call plugin for Janus, allowing two | |
* WebRTC peers to call each other through the Janus core. The idea is to | |
* provide a similar service as the well known AppRTC demo (https://apprtc.appspot.com), | |
* but with the media flowing through a server rather than being peer-to-peer. | |
* | |
* The plugin provides a simple fake registration mechanism. A peer attaching | |
* to the plugin needs to specify a username, which acts as a "phone number": | |
* if the username is free, it is associated with the peer, which means | |
* he/she can be "called" using that username by another peer. Peers can | |
* either "call" another peer, by specifying their username, or wait for a call. | |
* The approach used by this plugin is similar to the one employed by the | |
* echo test one: all frames (RTP/RTCP) coming from one peer are relayed | |
* to the other. | |
* | |
* Just as in the janus_videocall.c plugin, there are knobs to control | |
* whether audio and/or video should be muted or not, and if the bitrate | |
* of the peer needs to be capped by means of REMB messages. | |
* | |
* \section vcallapi Video Call API | |
* | |
* All requests you can send in the Video Call API are asynchronous, | |
* which means all responses (successes and errors) will be delivered | |
* as events with the same transaction. | |
* | |
* The supported requests are \c list , \c register , \c call , | |
* \c accept , \c set and \c hangup . \c list allows you to get a list | |
* of all the registered peers; \c register can be used to register | |
* a username to call and be called; \c call is used to start a video | |
* call with somebody through the plugin, while \c accept is used to | |
* accept the call in case one is invited instead of inviting; \c set | |
* can be used to configure some call-related settings (e.g., a cap on | |
* the send bandwidth); finally, \c hangup can be used to terminate the | |
* communication at any time, either to hangup an ongoing call or to | |
* cancel/decline a call that hasn't started yet. | |
* | |
* The \c list request has to be formatted as follows: | |
* | |
\verbatim | |
{ | |
"request" : "list" | |
} | |
\endverbatim | |
* | |
* A successful request will result in an array of peers to be returned: | |
* | |
\verbatim | |
{ | |
"videocall" : "event", | |
"result" : { | |
"list": [ // Array of peers | |
"alice78", | |
"bob51", | |
// others | |
] | |
} | |
} | |
\endverbatim | |
* | |
* An error instead (and the same applies to all other requests, so this | |
* won't be repeated) would provide both an error code and a more verbose | |
* description of the cause of the issue: | |
* | |
\verbatim | |
{ | |
"videocall" : "event", | |
"error_code" : <numeric ID, check Macros below>, | |
"error" : "<error description as a string>" | |
} | |
\endverbatim | |
* | |
* To register a username to call and be called, the \c register request | |
* can be used. This works on a "first come, first served" basis: there's | |
* no authentication involved, you just specify the username you'd like | |
* to use and, if free, it's assigned to you. Notice that there's no | |
* way to unregister: you have to close the handle to free the username. | |
* The \c register request has to be formatted as follows: | |
* | |
\verbatim | |
{ | |
"request" : "register", | |
"username" : "<desired unique username>", | |
"displayname" : "display name" | |
} | |
\endverbatim | |
* | |
* If successul, this will result in a \c registered event: | |
* | |
\verbatim | |
{ | |
"videocall" : "event", | |
"result" : { | |
"event" : "registered", | |
"username" : "<same username, registered>" | |
} | |
} | |
\endverbatim | |
* | |
* Once you're registered, you can either start a new call or wait to | |
* be called by someone else who knows your username. To start a new | |
* call, the \c call request can be used: this request must be attached | |
* to a JSEP offer containing the WebRTC-related info to setup a new | |
* media session. A \c call request has to be formatted as follows: | |
* | |
\verbatim | |
{ | |
"request" : "call", | |
"username" : "<username to call>" | |
} | |
\endverbatim | |
* | |
* If successul, this will result in a \c calling event: | |
* | |
\verbatim | |
{ | |
"videocall" : "event", | |
"result" : { | |
"event" : "calling", | |
"username" : "<same username, registered>" | |
} | |
} | |
\endverbatim | |
* | |
* At the same time, the user being called will receive an | |
* \c incomingcall event | |
* | |
\verbatim | |
{ | |
"videocall" : "event", | |
"result" : { | |
"event" : "incomingcall", | |
"username" : "<your username>", | |
"displayname" : "display name" | |
} | |
} | |
\endverbatim | |
* | |
* To accept the call, the \c accept request can be used. This request | |
* must be attached to a JSEP answer containing the WebRTC-related | |
* information to complete the actual PeerConnection setup. A \c accept | |
* request has to be formatted as follows: | |
* | |
\verbatim | |
{ | |
"request" : "accept" | |
} | |
\endverbatim | |
* | |
* If successul, both the caller and the callee will receive an | |
* \c accepted event to notify them about the success of the signalling: | |
* | |
\verbatim | |
{ | |
"videocall" : "event", | |
"result" : { | |
"event" : "accepted", | |
"username" : "<caller username>" | |
} | |
} | |
\endverbatim | |
* | |
* At this point, the media-related settings of the call can be modified | |
* on either side by means of a \c set request, which acts pretty much | |
* as the one in the \ref echoapi . The \c set request has to be | |
* formatted as follows. All the attributes (except \c request) are | |
* optional, so any request can contain a subset of them: | |
* | |
\verbatim | |
{ | |
"request" : "set", | |
"audio" : true|false, | |
"video" : true|false, | |
"bitrate" : <numeric bitrate value>, | |
"record" : true|false, | |
"filename" : <base path/filename to use for the recording>, | |
"substream" : <substream to receive (0-2), in case simulcasting is enabled>, | |
"temporal" : <temporal layers to receive (0-2), in case simulcasting is enabled>, | |
"fallback" : <How much time (in us, default 250000) without receiving packets will make us drop to the substream below> | |
} | |
\endverbatim | |
* | |
* \c audio instructs the plugin to do or do not relay audio frames; | |
* \c video does the same for video; \c bitrate caps the bandwidth to | |
* force on the browser encoding side (e.g., 128000 for 128kbps); | |
* \c record enables or disables the recording of this peer; in case | |
* recording is enabled, \c filename allows to specify a base | |
* path/filename to use for the files (-audio.mjr, -video.mjr and -data.mjr | |
* are automatically appended). Beware that enabling the recording only | |
* records this user's contribution, and not the whole call: to record | |
* both sides, you need to enable recording for both the peers in the | |
* call. Finally, in case the call uses simulcasting, \c substream and | |
* \c temporal can be used to manually pick which substream and/or temporal | |
* layer should be received from the peer. | |
* | |
* A successful request will result in a \c set event: | |
* | |
\verbatim | |
{ | |
"videocall" : "event", | |
"result" : { | |
"event" : "set" | |
} | |
} | |
\endverbatim | |
* | |
* Notice that the \c set request is also what you use when you want | |
* to renegotiate a session, e.g., for the purpose of adding/removing | |
* media streams or forcing an ICE restart. In that case, even an empty | |
* \c set request is fine, as long as it accompanies a new JSEP offer | |
* or answer (depending on who originated the session update). The user | |
* receiving the updated JSEP offer/answer will get an \c update event: | |
* | |
\verbatim | |
{ | |
"videocall" : "event", | |
"result" : { | |
"event" : "update", | |
} | |
} | |
\endverbatim | |
* | |
* To decline an incoming call, cancel an attempt to call or simply | |
* hangup an ongoing conversation, the \c hangup request can be used, | |
* which has to be formatted as follows: | |
* | |
\verbatim | |
{ | |
"request" : "hangup" | |
} | |
\endverbatim | |
* | |
* Whatever the reason of a call being closed (e.g., a \c hangup request, | |
* a PeerConnection being closed, or something else), both parties in | |
* the communication will receive a \c hangup event: | |
* | |
\verbatim | |
{ | |
"videocall" : "event", | |
"result" : { | |
"event" : "hangup", | |
"username" : "<username of who closed the communication>", | |
"reason" : "<description of what happened>" | |
} | |
} | |
\endverbatim | |
*/ | |
#include "plugin.h" | |
#include <jansson.h> | |
#include "../debug.h" | |
#include "../apierror.h" | |
#include "../config.h" | |
#include "../mutex.h" | |
#include "../record.h" | |
#include "../rtp.h" | |
#include "../rtcp.h" | |
#include "../sdp-utils.h" | |
#include "../utils.h" | |
/* Plugin information */ | |
#define JANUS_VIDEOCALL_VERSION 6 | |
#define JANUS_VIDEOCALL_VERSION_STRING "0.0.6" | |
#define JANUS_VIDEOCALL_DESCRIPTION "This is a simple video call plugin for Janus, allowing two WebRTC peers to call each other through a server." | |
#define JANUS_VIDEOCALL_NAME "JANUS VideoCall plugin" | |
#define JANUS_VIDEOCALL_AUTHOR "Meetecho s.r.l." | |
#define JANUS_VIDEOCALL_PACKAGE "janus.plugin.videocall" | |
/* Plugin methods */ | |
janus_plugin *create(void); | |
int janus_videocall_init(janus_callbacks *callback, const char *config_path); | |
void janus_videocall_destroy(void); | |
int janus_videocall_get_api_compatibility(void); | |
int janus_videocall_get_version(void); | |
const char *janus_videocall_get_version_string(void); | |
const char *janus_videocall_get_description(void); | |
const char *janus_videocall_get_name(void); | |
const char *janus_videocall_get_author(void); | |
const char *janus_videocall_get_package(void); | |
void janus_videocall_create_session(janus_plugin_session *handle, int *error); | |
struct janus_plugin_result *janus_videocall_handle_message(janus_plugin_session *handle, char *transaction, json_t *message, json_t *jsep); | |
void janus_videocall_setup_media(janus_plugin_session *handle); | |
void janus_videocall_incoming_rtp(janus_plugin_session *handle, janus_plugin_rtp *packet); | |
void janus_videocall_incoming_rtcp(janus_plugin_session *handle, janus_plugin_rtcp *packet); | |
void janus_videocall_incoming_data(janus_plugin_session *handle, janus_plugin_data *packet); | |
void janus_videocall_data_ready(janus_plugin_session *handle); | |
void janus_videocall_slow_link(janus_plugin_session *handle, int uplink, int video); | |
void janus_videocall_hangup_media(janus_plugin_session *handle); | |
void janus_videocall_destroy_session(janus_plugin_session *handle, int *error); | |
json_t *janus_videocall_query_session(janus_plugin_session *handle); | |
/* Plugin setup */ | |
static janus_plugin janus_videocall_plugin = | |
JANUS_PLUGIN_INIT ( | |
.init = janus_videocall_init, | |
.destroy = janus_videocall_destroy, | |
.get_api_compatibility = janus_videocall_get_api_compatibility, | |
.get_version = janus_videocall_get_version, | |
.get_version_string = janus_videocall_get_version_string, | |
.get_description = janus_videocall_get_description, | |
.get_name = janus_videocall_get_name, | |
.get_author = janus_videocall_get_author, | |
.get_package = janus_videocall_get_package, | |
.create_session = janus_videocall_create_session, | |
.handle_message = janus_videocall_handle_message, | |
.setup_media = janus_videocall_setup_media, | |
.incoming_rtp = janus_videocall_incoming_rtp, | |
.incoming_rtcp = janus_videocall_incoming_rtcp, | |
.incoming_data = janus_videocall_incoming_data, | |
.data_ready = janus_videocall_data_ready, | |
.slow_link = janus_videocall_slow_link, | |
.hangup_media = janus_videocall_hangup_media, | |
.destroy_session = janus_videocall_destroy_session, | |
.query_session = janus_videocall_query_session, | |
); | |
/* Plugin creator */ | |
janus_plugin *create(void) { | |
JANUS_LOG(LOG_VERB, "%s created!\n", JANUS_VIDEOCALL_NAME); | |
return &janus_videocall_plugin; | |
} | |
/* Parameter validation */ | |
static struct janus_json_parameter request_parameters[] = { | |
{"request", JSON_STRING, JANUS_JSON_PARAM_REQUIRED} | |
}; | |
static struct janus_json_parameter username_parameters[] = { | |
{"username", JSON_STRING, JANUS_JSON_PARAM_REQUIRED} | |
}; | |
static struct janus_json_parameter set_parameters[] = { | |
{"audio", JANUS_JSON_BOOL, 0}, | |
{"video", JANUS_JSON_BOOL, 0}, | |
{"bitrate", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE}, | |
{"record", JANUS_JSON_BOOL, 0}, | |
{"filename", JSON_STRING, 0}, | |
{"restart", JANUS_JSON_BOOL, 0} | |
}; | |
/* Useful stuff */ | |
static volatile gint initialized = 0, stopping = 0; | |
static gboolean notify_events = TRUE; | |
static janus_callbacks *gateway = NULL; | |
static GThread *handler_thread; | |
static void *janus_videocall_handler(void *data); | |
static void janus_videocall_hangup_media_internal(janus_plugin_session *handle); | |
typedef struct janus_videocall_message { | |
janus_plugin_session *handle; | |
char *transaction; | |
json_t *message; | |
json_t *jsep; | |
} janus_videocall_message; | |
static GAsyncQueue *messages = NULL; | |
static janus_videocall_message exit_message; | |
typedef struct janus_videocall_session { | |
janus_plugin_session *handle; | |
gchar *username; | |
gchar *displayname; | |
gboolean has_audio; | |
gboolean has_video; | |
gboolean has_data; | |
gboolean audio_active; | |
gboolean video_active; | |
janus_audiocodec acodec;/* Codec used for audio, if available */ | |
janus_videocodec vcodec;/* Codec used for video, if available */ | |
uint32_t bitrate, peer_bitrate; | |
guint16 slowlink_count; | |
struct janus_videocall_session *peer; | |
janus_rtp_switching_context context; | |
uint32_t ssrc[3]; /* Only needed in case VP8 (or H.264) simulcasting is involved */ | |
char *rid[3]; /* Only needed if simulcasting is rid-based */ | |
janus_rtp_simulcasting_context sim_context; | |
janus_vp8_simulcast_context vp8_context; | |
janus_recorder *arc; /* The Janus recorder instance for this user's audio, if enabled */ | |
janus_recorder *vrc; /* The Janus recorder instance for this user's video, if enabled */ | |
janus_recorder *drc; /* The Janus recorder instance for this user's data, if enabled */ | |
gboolean e2ee; /* Whether media is encrypted, e.g., using Insertable Streams */ | |
janus_mutex rec_mutex; /* Mutex to protect the recorders from race conditions */ | |
volatile gint incall; | |
volatile gint dataready; | |
volatile gint hangingup; | |
volatile gint destroyed; | |
janus_refcount ref; | |
} janus_videocall_session; | |
static GHashTable *sessions = NULL, *usernames = NULL; | |
static janus_mutex sessions_mutex = JANUS_MUTEX_INITIALIZER; | |
static void janus_videocall_session_destroy(janus_videocall_session *session) { | |
if(session && g_atomic_int_compare_and_exchange(&session->destroyed, 0, 1)) | |
janus_refcount_decrease(&session->ref); | |
} | |
static void janus_videocall_session_unref(janus_videocall_session *session) { | |
if(session) | |
janus_refcount_decrease(&session->ref); | |
} | |
static void janus_videocall_session_free(const janus_refcount *session_ref) { | |
janus_videocall_session *session = janus_refcount_containerof(session_ref, janus_videocall_session, ref); | |
/* Remove the reference to the core plugin session */ | |
janus_refcount_decrease(&session->handle->ref); | |
/* This session can be destroyed, free all the resources */ | |
g_free(session->username); | |
g_free(session); | |
} | |
static void janus_videocall_message_free(janus_videocall_message *msg) { | |
if(!msg || msg == &exit_message) | |
return; | |
if(msg->handle && msg->handle->plugin_handle) { | |
janus_videocall_session *session = (janus_videocall_session *)msg->handle->plugin_handle; | |
janus_refcount_decrease(&session->ref); | |
} | |
msg->handle = NULL; | |
g_free(msg->transaction); | |
msg->transaction = NULL; | |
if(msg->message) | |
json_decref(msg->message); | |
msg->message = NULL; | |
if(msg->jsep) | |
json_decref(msg->jsep); | |
msg->jsep = NULL; | |
g_free(msg); | |
} | |
/* Error codes */ | |
#define JANUS_VIDEOCALL_ERROR_UNKNOWN_ERROR 499 | |
#define JANUS_VIDEOCALL_ERROR_NO_MESSAGE 470 | |
#define JANUS_VIDEOCALL_ERROR_INVALID_JSON 471 | |
#define JANUS_VIDEOCALL_ERROR_INVALID_REQUEST 472 | |
#define JANUS_VIDEOCALL_ERROR_REGISTER_FIRST 473 | |
#define JANUS_VIDEOCALL_ERROR_INVALID_ELEMENT 474 | |
#define JANUS_VIDEOCALL_ERROR_MISSING_ELEMENT 475 | |
#define JANUS_VIDEOCALL_ERROR_USERNAME_TAKEN 476 | |
#define JANUS_VIDEOCALL_ERROR_ALREADY_REGISTERED 477 | |
#define JANUS_VIDEOCALL_ERROR_NO_SUCH_USERNAME 478 | |
#define JANUS_VIDEOCALL_ERROR_USE_ECHO_TEST 479 | |
#define JANUS_VIDEOCALL_ERROR_ALREADY_IN_CALL 480 | |
#define JANUS_VIDEOCALL_ERROR_NO_CALL 481 | |
#define JANUS_VIDEOCALL_ERROR_MISSING_SDP 482 | |
#define JANUS_VIDEOCALL_ERROR_INVALID_SDP 483 | |
/* Plugin implementation */ | |
int janus_videocall_init(janus_callbacks *callback, const char *config_path) { | |
if(g_atomic_int_get(&stopping)) { | |
/* Still stopping from before */ | |
return -1; | |
} | |
if(callback == NULL || config_path == NULL) { | |
/* Invalid arguments */ | |
return -1; | |
} | |
/* Read configuration */ | |
char filename[255]; | |
g_snprintf(filename, 255, "%s/%s.jcfg", config_path, JANUS_VIDEOCALL_PACKAGE); | |
JANUS_LOG(LOG_VERB, "Configuration file: %s\n", filename); | |
janus_config *config = janus_config_parse(filename); | |
if(config == NULL) { | |
JANUS_LOG(LOG_WARN, "Couldn't find .jcfg configuration file (%s), trying .cfg\n", JANUS_VIDEOCALL_PACKAGE); | |
g_snprintf(filename, 255, "%s/%s.cfg", config_path, JANUS_VIDEOCALL_PACKAGE); | |
JANUS_LOG(LOG_VERB, "Configuration file: %s\n", filename); | |
config = janus_config_parse(filename); | |
} | |
if(config != NULL) { | |
janus_config_print(config); | |
janus_config_category *config_general = janus_config_get_create(config, NULL, janus_config_type_category, "general"); | |
janus_config_item *events = janus_config_get(config, config_general, janus_config_type_item, "events"); | |
if(events != NULL && events->value != NULL) | |
notify_events = janus_is_true(events->value); | |
if(!notify_events && callback->events_is_enabled()) { | |
JANUS_LOG(LOG_WARN, "Notification of events to handlers disabled for %s\n", JANUS_VIDEOCALL_NAME); | |
} | |
} | |
janus_config_destroy(config); | |
config = NULL; | |
sessions = g_hash_table_new_full(NULL, NULL, NULL, (GDestroyNotify)janus_videocall_session_destroy); | |
usernames = g_hash_table_new_full(g_str_hash, g_str_equal, | |
(GDestroyNotify)g_free, (GDestroyNotify)janus_videocall_session_unref); | |
messages = g_async_queue_new_full((GDestroyNotify) janus_videocall_message_free); | |
/* This is the callback we'll need to invoke to contact the Janus core */ | |
gateway = callback; | |
g_atomic_int_set(&initialized, 1); | |
/* Launch the thread that will handle incoming messages */ | |
GError *error = NULL; | |
handler_thread = g_thread_try_new("videocall handler", janus_videocall_handler, NULL, &error); | |
if(error != NULL) { | |
g_atomic_int_set(&initialized, 0); | |
JANUS_LOG(LOG_ERR, "Got error %d (%s) trying to launch the VideoCall handler thread...\n", | |
error->code, error->message ? error->message : "??"); | |
g_error_free(error); | |
return -1; | |
} | |
JANUS_LOG(LOG_INFO, "%s initialized!\n", JANUS_VIDEOCALL_NAME); | |
return 0; | |
} | |
void janus_videocall_destroy(void) { | |
if(!g_atomic_int_get(&initialized)) | |
return; | |
g_atomic_int_set(&stopping, 1); | |
g_async_queue_push(messages, &exit_message); | |
if(handler_thread != NULL) { | |
g_thread_join(handler_thread); | |
handler_thread = NULL; | |
} | |
/* FIXME We should destroy the sessions cleanly */ | |
janus_mutex_lock(&sessions_mutex); | |
g_hash_table_destroy(sessions); | |
sessions = NULL; | |
g_hash_table_destroy(usernames); | |
usernames = NULL; | |
janus_mutex_unlock(&sessions_mutex); | |
g_async_queue_unref(messages); | |
messages = NULL; | |
g_atomic_int_set(&initialized, 0); | |
g_atomic_int_set(&stopping, 0); | |
JANUS_LOG(LOG_INFO, "%s destroyed!\n", JANUS_VIDEOCALL_NAME); | |
} | |
int janus_videocall_get_api_compatibility(void) { | |
/* Important! This is what your plugin MUST always return: don't lie here or bad things will happen */ | |
return JANUS_PLUGIN_API_VERSION; | |
} | |
int janus_videocall_get_version(void) { | |
return JANUS_VIDEOCALL_VERSION; | |
} | |
const char *janus_videocall_get_version_string(void) { | |
return JANUS_VIDEOCALL_VERSION_STRING; | |
} | |
const char *janus_videocall_get_description(void) { | |
return JANUS_VIDEOCALL_DESCRIPTION; | |
} | |
const char *janus_videocall_get_name(void) { | |
return JANUS_VIDEOCALL_NAME; | |
} | |
const char *janus_videocall_get_author(void) { | |
return JANUS_VIDEOCALL_AUTHOR; | |
} | |
const char *janus_videocall_get_package(void) { | |
return JANUS_VIDEOCALL_PACKAGE; | |
} | |
static janus_videocall_session *janus_videocall_lookup_session(janus_plugin_session *handle) { | |
janus_videocall_session *session = NULL; | |
if(g_hash_table_contains(sessions, handle)) { | |
session = (janus_videocall_session *)handle->plugin_handle; | |
} | |
return session; | |
} | |
void janus_videocall_create_session(janus_plugin_session *handle, int *error) { | |
if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized)) { | |
*error = -1; | |
return; | |
} | |
janus_videocall_session *session = g_malloc0(sizeof(janus_videocall_session)); | |
session->handle = handle; | |
session->has_audio = FALSE; | |
session->has_video = FALSE; | |
session->has_data = FALSE; | |
session->audio_active = TRUE; | |
session->video_active = TRUE; | |
session->bitrate = 0; /* No limit */ | |
session->peer_bitrate = 0; | |
session->peer = NULL; | |
session->username = NULL; | |
session->displayname = NULL; | |
janus_rtp_switching_context_reset(&session->context); | |
janus_rtp_simulcasting_context_reset(&session->sim_context); | |
janus_vp8_simulcast_context_reset(&session->vp8_context); | |
janus_mutex_init(&session->rec_mutex); | |
g_atomic_int_set(&session->incall, 0); | |
g_atomic_int_set(&session->hangingup, 0); | |
g_atomic_int_set(&session->destroyed, 0); | |
handle->plugin_handle = session; | |
janus_refcount_init(&session->ref, janus_videocall_session_free); | |
janus_mutex_lock(&sessions_mutex); | |
g_hash_table_insert(sessions, handle, session); | |
janus_mutex_unlock(&sessions_mutex); | |
return; | |
} | |
void janus_videocall_destroy_session(janus_plugin_session *handle, int *error) { | |
if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized)) { | |
*error = -1; | |
return; | |
} | |
janus_mutex_lock(&sessions_mutex); | |
janus_videocall_session *session = janus_videocall_lookup_session(handle); | |
if(!session) { | |
janus_mutex_unlock(&sessions_mutex); | |
JANUS_LOG(LOG_ERR, "No session associated with this handle...\n"); | |
*error = -2; | |
return; | |
} | |
JANUS_LOG(LOG_VERB, "Removing VideoCall user %s session...\n", session->username ? session->username : "'unknown'"); | |
janus_videocall_hangup_media_internal(handle); | |
if(session->username != NULL) { | |
int res = g_hash_table_remove(usernames, (gpointer)session->username); | |
JANUS_LOG(LOG_VERB, " -- Removed: %d\n", res); | |
} | |
g_hash_table_remove(sessions, handle); | |
janus_mutex_unlock(&sessions_mutex); | |
return; | |
} | |
json_t *janus_videocall_query_session(janus_plugin_session *handle) { | |
if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized)) { | |
return NULL; | |
} | |
janus_mutex_lock(&sessions_mutex); | |
janus_videocall_session *session = janus_videocall_lookup_session(handle); | |
if(!session) { | |
janus_mutex_unlock(&sessions_mutex); | |
JANUS_LOG(LOG_ERR, "No session associated with this handle...\n"); | |
return NULL; | |
} | |
janus_refcount_increase(&session->ref); | |
janus_mutex_unlock(&sessions_mutex); | |
/* Provide some generic info, e.g., if we're in a call and with whom */ | |
janus_videocall_session *peer = session->peer; | |
json_t *info = json_object(); | |
json_object_set_new(info, "state", json_string(session->peer ? "incall" : "idle")); | |
json_object_set_new(info, "username", session->username ? json_string(session->username) : NULL); | |
if(peer) { | |
json_object_set_new(info, "peer", peer->username ? json_string(peer->username) : NULL); | |
json_object_set_new(info, "audio_active", session->audio_active ? json_true() : json_false()); | |
json_object_set_new(info, "video_active", session->video_active ? json_true() : json_false()); | |
if(session->acodec != JANUS_AUDIOCODEC_NONE) | |
json_object_set_new(info, "audio_codec", json_string(janus_audiocodec_name(session->acodec))); | |
if(session->vcodec != JANUS_VIDEOCODEC_NONE) | |
json_object_set_new(info, "video_codec", json_string(janus_videocodec_name(session->vcodec))); | |
json_object_set_new(info, "video_active", session->video_active ? json_true() : json_false()); | |
json_object_set_new(info, "bitrate", json_integer(session->bitrate)); | |
json_object_set_new(info, "peer-bitrate", json_integer(session->peer_bitrate)); | |
json_object_set_new(info, "slowlink_count", json_integer(session->slowlink_count)); | |
} | |
if(session->ssrc[0] != 0 || session->rid[0] != NULL) { | |
json_object_set_new(info, "simulcast", json_true()); | |
} | |
if(peer && (peer->ssrc[0] != 0 || peer->rid[0] != NULL)) { | |
json_object_set_new(info, "simulcast-peer", json_true()); | |
json_object_set_new(info, "substream", json_integer(session->sim_context.substream)); | |
json_object_set_new(info, "substream-target", json_integer(session->sim_context.substream_target)); | |
json_object_set_new(info, "temporal-layer", json_integer(session->sim_context.templayer)); | |
json_object_set_new(info, "temporal-layer-target", json_integer(session->sim_context.templayer_target)); | |
if(session->sim_context.drop_trigger > 0) | |
json_object_set_new(info, "fallback", json_integer(session->sim_context.drop_trigger)); | |
} | |
if(session->arc || session->vrc || session->drc) { | |
json_t *recording = json_object(); | |
if(session->arc && session->arc->filename) | |
json_object_set_new(recording, "audio", json_string(session->arc->filename)); | |
if(session->vrc && session->vrc->filename) | |
json_object_set_new(recording, "video", json_string(session->vrc->filename)); | |
if(session->drc && session->drc->filename) | |
json_object_set_new(recording, "data", json_string(session->drc->filename)); | |
json_object_set_new(info, "recording", recording); | |
} | |
json_object_set_new(info, "incall", json_integer(g_atomic_int_get(&session->incall))); | |
if(session->e2ee) | |
json_object_set_new(info, "e2ee", json_true()); | |
json_object_set_new(info, "hangingup", json_integer(g_atomic_int_get(&session->hangingup))); | |
json_object_set_new(info, "destroyed", json_integer(g_atomic_int_get(&session->destroyed))); | |
janus_refcount_decrease(&session->ref); | |
return info; | |
} | |
struct janus_plugin_result *janus_videocall_handle_message(janus_plugin_session *handle, char *transaction, json_t *message, json_t *jsep) { | |
if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized)) | |
return janus_plugin_result_new(JANUS_PLUGIN_ERROR, g_atomic_int_get(&stopping) ? "Shutting down" : "Plugin not initialized", NULL); | |
janus_mutex_lock(&sessions_mutex); | |
janus_videocall_session *session = janus_videocall_lookup_session(handle); | |
if(!session) { | |
janus_mutex_unlock(&sessions_mutex); | |
return janus_plugin_result_new(JANUS_PLUGIN_ERROR, "No session associated with this handle", NULL); | |
} | |
/* Increase the reference counter for this session: we'll decrease it after we handle the message */ | |
janus_refcount_increase(&session->ref); | |
janus_mutex_unlock(&sessions_mutex); | |
janus_videocall_message *msg = g_malloc(sizeof(janus_videocall_message)); | |
msg->handle = handle; | |
msg->transaction = transaction; | |
msg->message = message; | |
msg->jsep = jsep; | |
g_async_queue_push(messages, msg); | |
/* All the requests to this plugin are handled asynchronously */ | |
return janus_plugin_result_new(JANUS_PLUGIN_OK_WAIT, NULL, NULL); | |
} | |
void janus_videocall_setup_media(janus_plugin_session *handle) { | |
JANUS_LOG(LOG_INFO, "[%s-%p] WebRTC media is now available\n", JANUS_VIDEOCALL_PACKAGE, handle); | |
if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized)) | |
return; | |
janus_mutex_lock(&sessions_mutex); | |
janus_videocall_session *session = janus_videocall_lookup_session(handle); | |
if(!session) { | |
janus_mutex_unlock(&sessions_mutex); | |
JANUS_LOG(LOG_ERR, "No session associated with this handle...\n"); | |
return; | |
} | |
if(g_atomic_int_get(&session->destroyed)) { | |
janus_mutex_unlock(&sessions_mutex); | |
return; | |
} | |
g_atomic_int_set(&session->hangingup, 0); | |
janus_mutex_unlock(&sessions_mutex); | |
/* We really don't care, as we only relay RTP/RTCP we get in the first place anyway */ | |
} | |
void janus_videocall_incoming_rtp(janus_plugin_session *handle, janus_plugin_rtp *packet) { | |
if(handle == NULL || g_atomic_int_get(&handle->stopped) || g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized)) | |
return; | |
if(gateway) { | |
/* Honour the audio/video active flags */ | |
janus_videocall_session *session = (janus_videocall_session *)handle->plugin_handle; | |
if(!session) { | |
JANUS_LOG(LOG_ERR, "No session associated with this handle...\n"); | |
return; | |
} | |
janus_videocall_session *peer = session->peer; | |
if(!peer) { | |
JANUS_LOG(LOG_ERR, "Session has no peer...\n"); | |
return; | |
} | |
if(g_atomic_int_get(&session->destroyed) || g_atomic_int_get(&peer->destroyed)) | |
return; | |
gboolean video = packet->video; | |
char *buf = packet->buffer; | |
uint16_t len = packet->length; | |
if(video && session->video_active && (session->ssrc[0] != 0 || session->rid[0] != NULL)) { | |
/* Handle simulcast: backup the header information first */ | |
janus_rtp_header *header = (janus_rtp_header *)buf; | |
uint32_t seq_number = ntohs(header->seq_number); | |
uint32_t timestamp = ntohl(header->timestamp); | |
uint32_t ssrc = ntohl(header->ssrc); | |
/* Process this packet: don't relay if it's not the SSRC/layer we wanted to handle | |
* The caveat is that the targets in OUR simulcast context are the PEER's targets */ | |
gboolean relay = janus_rtp_simulcasting_context_process_rtp(&peer->sim_context, | |
buf, len, session->ssrc, session->rid, session->vcodec, &peer->context); | |
/* Do we need to drop this? */ | |
if(!relay) | |
return; | |
if(peer->sim_context.need_pli) { | |
/* Send a PLI */ | |
JANUS_LOG(LOG_VERB, "We need a PLI for the simulcast context\n"); | |
gateway->send_pli(session->handle); | |
} | |
/* Any event we should notify? */ | |
if(peer->sim_context.changed_substream) { | |
/* Notify the user about the substream change */ | |
json_t *event = json_object(); | |
json_object_set_new(event, "videocall", json_string("event")); | |
json_t *result = json_object(); | |
json_object_set_new(result, "event", json_string("simulcast")); | |
json_object_set_new(result, "videocodec", json_string(janus_videocodec_name(session->vcodec))); | |
json_object_set_new(result, "substream", json_integer(session->sim_context.substream)); | |
json_object_set_new(event, "result", result); | |
gateway->push_event(peer->handle, &janus_videocall_plugin, NULL, event, NULL); | |
json_decref(event); | |
} | |
if(peer->sim_context.changed_temporal) { | |
/* Notify the user about the temporal layer change */ | |
json_t *event = json_object(); | |
json_object_set_new(event, "videocall", json_string("event")); | |
json_t *result = json_object(); | |
json_object_set_new(result, "event", json_string("simulcast")); | |
json_object_set_new(result, "videocodec", json_string(janus_videocodec_name(session->vcodec))); | |
json_object_set_new(result, "temporal", json_integer(session->sim_context.templayer)); | |
json_object_set_new(event, "result", result); | |
gateway->push_event(peer->handle, &janus_videocall_plugin, NULL, event, NULL); | |
json_decref(event); | |
} | |
/* If we got here, update the RTP header and send the packet */ | |
janus_rtp_header_update(header, &peer->context, TRUE, 0); | |
if(session->vcodec == JANUS_VIDEOCODEC_VP8) { | |
int plen = 0; | |
char *payload = janus_rtp_payload(buf, len, &plen); | |
janus_vp8_simulcast_descriptor_update(payload, plen, &peer->vp8_context, peer->sim_context.changed_substream); | |
} | |
/* Save the frame if we're recording (and make sure the SSRC never changes even if the substream does) */ | |
header->ssrc = htonl(1); | |
janus_recorder_save_frame(session->vrc, buf, len); | |
/* Send the frame back */ | |
gateway->relay_rtp(peer->handle, packet); | |
/* Restore header or core statistics will be messed up */ | |
header->ssrc = htonl(ssrc); | |
header->timestamp = htonl(timestamp); | |
header->seq_number = htons(seq_number); | |
} else { | |
if((!video && session->audio_active) || (video && session->video_active)) { | |
/* Save the frame if we're recording */ | |
janus_recorder_save_frame(video ? session->vrc : session->arc, buf, len); | |
/* Forward the packet to the peer */ | |
gateway->relay_rtp(peer->handle, packet); | |
} | |
} | |
} | |
} | |
void janus_videocall_incoming_rtcp(janus_plugin_session *handle, janus_plugin_rtcp *packet) { | |
if(handle == NULL || g_atomic_int_get(&handle->stopped) || g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized)) | |
return; | |
if(gateway) { | |
janus_videocall_session *session = (janus_videocall_session *)handle->plugin_handle; | |
if(!session) { | |
JANUS_LOG(LOG_ERR, "No session associated with this handle...\n"); | |
return; | |
} | |
janus_videocall_session *peer = session->peer; | |
if(!peer) { | |
JANUS_LOG(LOG_ERR, "Session has no peer...\n"); | |
return; | |
} | |
if(g_atomic_int_get(&session->destroyed) || g_atomic_int_get(&peer->destroyed)) | |
return; | |
guint32 bitrate = janus_rtcp_get_remb(packet->buffer, packet->length); | |
if(bitrate > 0) { | |
/* If a REMB arrived, make sure we cap it to our configuration, and send it as a video RTCP */ | |
session->peer_bitrate = bitrate; | |
/* No limit ~= 10000000 */ | |
gateway->send_remb(handle, session->bitrate ? session->bitrate : 10000000); | |
return; | |
} | |
gateway->relay_rtcp(peer->handle, packet); | |
} | |
} | |
void janus_videocall_incoming_data(janus_plugin_session *handle, janus_plugin_data *packet) { | |
if(handle == NULL || g_atomic_int_get(&handle->stopped) || g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized)) | |
return; | |
if(gateway) { | |
janus_videocall_session *session = (janus_videocall_session *)handle->plugin_handle; | |
if(!session) { | |
JANUS_LOG(LOG_ERR, "No session associated with this handle...\n"); | |
return; | |
} | |
janus_videocall_session *peer = session->peer; | |
if(!peer) { | |
JANUS_LOG(LOG_ERR, "Session has no peer...\n"); | |
return; | |
} | |
if(g_atomic_int_get(&session->destroyed) || g_atomic_int_get(&peer->destroyed) || | |
!g_atomic_int_get(&peer->dataready)) | |
return; | |
if(packet->buffer == NULL || packet->length == 0) | |
return; | |
char *label = packet->label; | |
char *buf = packet->buffer; | |
uint16_t len = packet->length; | |
JANUS_LOG(LOG_VERB, "Got a %s DataChannel message (%d bytes) to forward\n", | |
!packet->binary ? "text" : "binary", len); | |
/* Save the frame if we're recording */ | |
janus_recorder_save_frame(session->drc, buf, len); | |
/* Forward the packet to the peer */ | |
janus_plugin_data r = { | |
.label = label, | |
.protocol = NULL, | |
.binary = packet->binary, | |
.buffer = buf, | |
.length = len | |
}; | |
gateway->relay_data(peer->handle, &r); | |
} | |
} | |
void janus_videocall_data_ready(janus_plugin_session *handle) { | |
if(handle == NULL || g_atomic_int_get(&handle->stopped) || | |
g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized) || !gateway) | |
return; | |
/* Data channels are writable */ | |
janus_videocall_session *session = (janus_videocall_session *)handle->plugin_handle; | |
if(!session || g_atomic_int_get(&session->destroyed) || g_atomic_int_get(&session->hangingup)) | |
return; | |
if(g_atomic_int_compare_and_exchange(&session->dataready, 0, 1)) { | |
JANUS_LOG(LOG_INFO, "[%s-%p] Data channel available\n", JANUS_VIDEOCALL_PACKAGE, handle); | |
} | |
} | |
void janus_videocall_slow_link(janus_plugin_session *handle, int uplink, int video) { | |
/* The core is informing us that our peer got or sent too many NACKs, are we pushing media too hard? */ | |
if(handle == NULL || g_atomic_int_get(&handle->stopped) || g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized)) | |
return; | |
janus_videocall_session *session = (janus_videocall_session *)handle->plugin_handle; | |
if(!session) { | |
JANUS_LOG(LOG_ERR, "No session associated with this handle...\n"); | |
return; | |
} | |
if(g_atomic_int_get(&session->destroyed)) | |
return; | |
session->slowlink_count++; | |
if(uplink && !video && !session->audio_active) { | |
/* We're not relaying audio and the peer is expecting it, so NACKs are normal */ | |
JANUS_LOG(LOG_VERB, "Getting a lot of lost packets (slow uplink) for audio, but that's expected, a configure disabled the audio forwarding\n"); | |
} else if(uplink && video && !session->video_active) { | |
/* We're not relaying video and the peer is expecting it, so NACKs are normal */ | |
JANUS_LOG(LOG_VERB, "Getting a lot of lost packets (slow uplink) for video, but that's expected, a configure disabled the video forwarding\n"); | |
} else { | |
JANUS_LOG(LOG_WARN, "Getting a lot of lost packets (slow %s) for %s\n", | |
uplink ? "uplink" : "downlink", video ? "video" : "audio"); | |
if(!uplink) { | |
/* Send an event on the handle to notify the application: it's | |
* up to the application to then choose a policy and enforce it */ | |
json_t *event = json_object(); | |
json_object_set_new(event, "videocall", json_string("event")); | |
/* Also add info on what the current bitrate cap is */ | |
json_t *result = json_object(); | |
json_object_set_new(result, "event", json_string("slow_link")); | |
json_object_set_new(result, "media", json_string(video ? "video" : "audio")); | |
if(video) | |
json_object_set_new(result, "current-bitrate", json_integer(session->bitrate)); | |
json_object_set_new(event, "result", result); | |
gateway->push_event(session->handle, &janus_videocall_plugin, NULL, event, NULL); | |
json_decref(event); | |
} | |
} | |
} | |
static void janus_videocall_recorder_close(janus_videocall_session *session) { | |
if(session->arc) { | |
janus_recorder *rc = session->arc; | |
session->arc = NULL; | |
janus_recorder_close(rc); | |
JANUS_LOG(LOG_INFO, "Closed audio recording %s\n", rc->filename ? rc->filename : "??"); | |
janus_recorder_destroy(rc); | |
} | |
if(session->vrc) { | |
janus_recorder *rc = session->vrc; | |
session->vrc = NULL; | |
janus_recorder_close(rc); | |
JANUS_LOG(LOG_INFO, "Closed video recording %s\n", rc->filename ? rc->filename : "??"); | |
janus_recorder_destroy(rc); | |
} | |
if(session->drc) { | |
janus_recorder *rc = session->drc; | |
session->drc = NULL; | |
janus_recorder_close(rc); | |
JANUS_LOG(LOG_INFO, "Closed data recording %s\n", rc->filename ? rc->filename : "??"); | |
janus_recorder_destroy(rc); | |
} | |
} | |
void janus_videocall_hangup_media(janus_plugin_session *handle) { | |
JANUS_LOG(LOG_INFO, "[%s-%p] No WebRTC media anymore\n", JANUS_VIDEOCALL_PACKAGE, handle); | |
janus_mutex_lock(&sessions_mutex); | |
janus_videocall_hangup_media_internal(handle); | |
janus_mutex_unlock(&sessions_mutex); | |
} | |
static void janus_videocall_hangup_media_internal(janus_plugin_session *handle) { | |
if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized)) | |
return; | |
janus_videocall_session *session = janus_videocall_lookup_session(handle); | |
if(!session) { | |
JANUS_LOG(LOG_ERR, "No session associated with this handle...\n"); | |
return; | |
} | |
if(g_atomic_int_get(&session->destroyed)) | |
return; | |
if(!g_atomic_int_compare_and_exchange(&session->hangingup, 0, 1)) | |
return; | |
g_atomic_int_set(&session->dataready, 0); | |
/* Get rid of the recorders, if available */ | |
janus_mutex_lock(&session->rec_mutex); | |
janus_videocall_recorder_close(session); | |
janus_mutex_unlock(&session->rec_mutex); | |
janus_videocall_session *peer = session->peer; | |
session->peer = NULL; | |
if(peer) { | |
/* Send event to our peer too */ | |
json_t *call = json_object(); | |
json_object_set_new(call, "videocall", json_string("event")); | |
json_t *calling = json_object(); | |
json_object_set_new(calling, "event", json_string("hangup")); | |
json_object_set_new(calling, "username", json_string(session->username)); | |
json_object_set_new(calling, "displayname", json_string(session->displayname)); | |
json_object_set_new(calling, "reason", json_string("Remote WebRTC hangup")); | |
json_object_set_new(call, "result", calling); | |
gateway->close_pc(peer->handle); | |
int ret = gateway->push_event(peer->handle, &janus_videocall_plugin, NULL, call, NULL); | |
JANUS_LOG(LOG_VERB, " >> Pushing event to peer: %d (%s)\n", ret, janus_get_api_error(ret)); | |
json_decref(call); | |
/* Also notify event handlers */ | |
if(notify_events && gateway->events_is_enabled()) { | |
json_t *info = json_object(); | |
json_object_set_new(info, "event", json_string("hangup")); | |
json_object_set_new(info, "reason", json_string("Remote WebRTC hangup")); | |
gateway->notify_event(&janus_videocall_plugin, peer->handle, info); | |
} | |
} | |
/* Reset controls */ | |
session->has_audio = FALSE; | |
session->has_video = FALSE; | |
session->has_data = FALSE; | |
session->audio_active = TRUE; | |
session->video_active = TRUE; | |
session->acodec = JANUS_AUDIOCODEC_NONE; | |
session->vcodec = JANUS_VIDEOCODEC_NONE; | |
session->bitrate = 0; | |
session->peer_bitrate = 0; | |
session->e2ee = FALSE; | |
int i=0; | |
for(i=0; i<3; i++) { | |
session->ssrc[i] = 0; | |
g_free(session->rid[i]); | |
session->rid[i] = NULL; | |
} | |
janus_rtp_switching_context_reset(&session->context); | |
janus_rtp_simulcasting_context_reset(&session->sim_context); | |
janus_vp8_simulcast_context_reset(&session->vp8_context); | |
if(g_atomic_int_compare_and_exchange(&session->incall, 1, 0) && peer) { | |
janus_refcount_decrease(&peer->ref); | |
} | |
janus_rtp_switching_context_reset(&session->context); | |
g_atomic_int_set(&session->hangingup, 0); | |
} | |
/* Thread to handle incoming messages */ | |
static void *janus_videocall_handler(void *data) { | |
JANUS_LOG(LOG_VERB, "Joining VideoCall handler thread\n"); | |
janus_videocall_message *msg = NULL; | |
int error_code = 0; | |
char error_cause[512]; | |
json_t *root = NULL; | |
while(g_atomic_int_get(&initialized) && !g_atomic_int_get(&stopping)) { | |
msg = g_async_queue_pop(messages); | |
if(msg == &exit_message) | |
break; | |
if(msg->handle == NULL) { | |
janus_videocall_message_free(msg); | |
continue; | |
} | |
janus_mutex_lock(&sessions_mutex); | |
janus_videocall_session *session = janus_videocall_lookup_session(msg->handle); | |
if(!session) { | |
janus_mutex_unlock(&sessions_mutex); | |
JANUS_LOG(LOG_ERR, "No session associated with this handle...\n"); | |
janus_videocall_message_free(msg); | |
continue; | |
} | |
if(g_atomic_int_get(&session->destroyed)) { | |
janus_mutex_unlock(&sessions_mutex); | |
janus_videocall_message_free(msg); | |
continue; | |
} | |
janus_mutex_unlock(&sessions_mutex); | |
/* Handle request */ | |
error_code = 0; | |
root = msg->message; | |
if(msg->message == NULL) { | |
JANUS_LOG(LOG_ERR, "No message??\n"); | |
error_code = JANUS_VIDEOCALL_ERROR_NO_MESSAGE; | |
g_snprintf(error_cause, 512, "%s", "No message??"); | |
goto error; | |
} | |
if(!json_is_object(root)) { | |
JANUS_LOG(LOG_ERR, "JSON error: not an object\n"); | |
error_code = JANUS_VIDEOCALL_ERROR_INVALID_JSON; | |
g_snprintf(error_cause, 512, "JSON error: not an object"); | |
goto error; | |
} | |
JANUS_VALIDATE_JSON_OBJECT(root, request_parameters, | |
error_code, error_cause, TRUE, | |
JANUS_VIDEOCALL_ERROR_MISSING_ELEMENT, JANUS_VIDEOCALL_ERROR_INVALID_ELEMENT); | |
if(error_code != 0) | |
goto error; | |
const char *msg_sdp_type = json_string_value(json_object_get(msg->jsep, "type")); | |
const char *msg_sdp = json_string_value(json_object_get(msg->jsep, "sdp")); | |
json_t *msg_e2ee = json_object_get(msg->jsep, "e2ee"); | |
if(json_is_true(msg_e2ee)) | |
session->e2ee = TRUE; | |
json_t *request = json_object_get(root, "request"); | |
const char *request_text = json_string_value(request); | |
json_t *result = NULL; | |
gboolean sdp_update = FALSE; | |
if(json_object_get(msg->jsep, "update") != NULL) | |
sdp_update = json_is_true(json_object_get(msg->jsep, "update")); | |
if(!strcasecmp(request_text, "list")) { | |
result = json_object(); | |
json_t *list = json_array(); | |
JANUS_LOG(LOG_VERB, "Request for the list of peers\n"); | |
/* Return a list of all available mountpoints */ | |
janus_mutex_lock(&sessions_mutex); | |
GHashTableIter iter; | |
gpointer value; | |
g_hash_table_iter_init(&iter, sessions); | |
while (g_hash_table_iter_next(&iter, NULL, &value)) { | |
janus_videocall_session *user = value; | |
if(user != NULL) { | |
janus_refcount_increase(&user->ref); | |
if(user->username != NULL) | |
json_array_append_new(list, json_string(user->username)); | |
janus_refcount_decrease(&user->ref); | |
} | |
} | |
json_object_set_new(result, "list", list); | |
janus_mutex_unlock(&sessions_mutex); | |
} else if(!strcasecmp(request_text, "register")) { | |
/* Map this handle to a username */ | |
if(session->username != NULL) { | |
JANUS_LOG(LOG_ERR, "Already registered (%s)\n", session->username); | |
error_code = JANUS_VIDEOCALL_ERROR_ALREADY_REGISTERED; | |
g_snprintf(error_cause, 512, "Already registered (%s)", session->username); | |
goto error; | |
} | |
JANUS_VALIDATE_JSON_OBJECT(root, username_parameters, | |
error_code, error_cause, TRUE, | |
JANUS_VIDEOCALL_ERROR_MISSING_ELEMENT, JANUS_VIDEOCALL_ERROR_INVALID_ELEMENT); | |
if(error_code != 0) | |
goto error; | |
json_t *username = json_object_get(root, "username"); | |
json_t *displayname = json_object_get(root, "displayname"); | |
const char *username_text = json_string_value(username); | |
const char *displayname_text = json_string_value(displayname); | |
janus_mutex_lock(&sessions_mutex); | |
if(g_hash_table_lookup(usernames, username_text) != NULL) { | |
janus_mutex_unlock(&sessions_mutex); | |
JANUS_LOG(LOG_ERR, "Username '%s' already taken\n", username_text); | |
error_code = JANUS_VIDEOCALL_ERROR_USERNAME_TAKEN; | |
g_snprintf(error_cause, 512, "Username '%s' already taken", username_text); | |
goto error; | |
} | |
session->username = g_strdup(username_text); | |
session->displayname = g_strdup(displayname_text); | |
janus_refcount_increase(&session->ref); | |
g_hash_table_insert(usernames, (gpointer)g_strdup(session->username), session); | |
janus_mutex_unlock(&sessions_mutex); | |
result = json_object(); | |
json_object_set_new(result, "event", json_string("registered")); | |
json_object_set_new(result, "username", json_string(username_text)); | |
/* Also notify event handlers */ | |
if(notify_events && gateway->events_is_enabled()) { | |
json_t *info = json_object(); | |
json_object_set_new(info, "event", json_string("registered")); | |
json_object_set_new(info, "username", json_string(username_text)); | |
gateway->notify_event(&janus_videocall_plugin, session->handle, info); | |
} | |
} else if(!strcasecmp(request_text, "call")) { | |
/* Call another peer */ | |
if(session->username == NULL) { | |
JANUS_LOG(LOG_ERR, "Register a username first\n"); | |
error_code = JANUS_VIDEOCALL_ERROR_REGISTER_FIRST; | |
g_snprintf(error_cause, 512, "Register a username first"); | |
/* Hangup the call attempt of the user */ | |
gateway->close_pc(session->handle); | |
goto error; | |
} | |
if(session->peer != NULL) { | |
JANUS_LOG(LOG_ERR, "Already in a call\n"); | |
error_code = JANUS_VIDEOCALL_ERROR_ALREADY_IN_CALL; | |
g_snprintf(error_cause, 512, "Already in a call"); | |
/* Hangup the call attempt of the user */ | |
gateway->close_pc(session->handle); | |
goto error; | |
} | |
if(!g_atomic_int_compare_and_exchange(&session->incall, 0, 1)) { | |
JANUS_LOG(LOG_ERR, "Already in a call (but no peer?)\n"); | |
error_code = JANUS_VIDEOCALL_ERROR_ALREADY_IN_CALL; | |
g_snprintf(error_cause, 512, "Already in a call (but no peer)"); | |
/* Hangup the call attempt of the user */ | |
gateway->close_pc(session->handle); | |
goto error; | |
} | |
JANUS_VALIDATE_JSON_OBJECT(root, username_parameters, | |
error_code, error_cause, TRUE, | |
JANUS_VIDEOCALL_ERROR_MISSING_ELEMENT, JANUS_VIDEOCALL_ERROR_INVALID_ELEMENT); | |
if(error_code != 0) { | |
/* Hangup the call attempt of the user */ | |
g_atomic_int_set(&session->incall, 0); | |
gateway->close_pc(session->handle); | |
goto error; | |
} | |
json_t *username = json_object_get(root, "username"); | |
const char *username_text = json_string_value(username); | |
if(!strcmp(username_text, session->username)) { | |
g_atomic_int_set(&session->incall, 0); | |
JANUS_LOG(LOG_ERR, "You can't call yourself... use the EchoTest for that\n"); | |
error_code = JANUS_VIDEOCALL_ERROR_USE_ECHO_TEST; | |
g_snprintf(error_cause, 512, "You can't call yourself... use the EchoTest for that"); | |
/* Hangup the call attempt of the user */ | |
gateway->close_pc(session->handle); | |
goto error; | |
} | |
janus_mutex_lock(&sessions_mutex); | |
janus_videocall_session *peer = g_hash_table_lookup(usernames, username_text); | |
if(peer == NULL || g_atomic_int_get(&peer->destroyed)) { | |
g_atomic_int_set(&session->incall, 0); | |
janus_mutex_unlock(&sessions_mutex); | |
JANUS_LOG(LOG_ERR, "Username '%s' doesn't exist\n", username_text); | |
error_code = JANUS_VIDEOCALL_ERROR_NO_SUCH_USERNAME; | |
g_snprintf(error_cause, 512, "Username '%s' doesn't exist", username_text); | |
/* Hangup the call attempt of the user */ | |
gateway->close_pc(session->handle); | |
goto error; | |
} | |
/* If the call attempt proceeds we keep the references */ | |
janus_refcount_increase(&session->ref); | |
janus_refcount_increase(&peer->ref); | |
if(g_atomic_int_get(&peer->incall) || peer->peer != NULL) { | |
if(g_atomic_int_compare_and_exchange(&session->incall, 1, 0) && peer) { | |
janus_refcount_decrease(&session->ref); | |
janus_refcount_decrease(&peer->ref); | |
} | |
janus_mutex_unlock(&sessions_mutex); | |
JANUS_LOG(LOG_VERB, "%s is busy\n", username_text); | |
result = json_object(); | |
json_object_set_new(result, "event", json_string("hangup")); | |
json_object_set_new(result, "username", json_string(session->username)); | |
json_object_set_new(result, "reason", json_string("User busy")); | |
/* Also notify event handlers */ | |
if(notify_events && gateway->events_is_enabled()) { | |
json_t *info = json_object(); | |
json_object_set_new(info, "event", json_string("hangup")); | |
json_object_set_new(info, "reason", json_string("User busy")); | |
gateway->notify_event(&janus_videocall_plugin, session->handle, info); | |
} | |
/* Hangup the call attempt of the user */ | |
gateway->close_pc(session->handle); | |
} else { | |
/* Any SDP to handle? if not, something's wrong */ | |
if(!msg_sdp) { | |
if(g_atomic_int_compare_and_exchange(&session->incall, 1, 0) && peer) { | |
janus_refcount_decrease(&session->ref); | |
janus_refcount_decrease(&peer->ref); | |
} | |
janus_mutex_unlock(&sessions_mutex); | |
JANUS_LOG(LOG_ERR, "Missing SDP\n"); | |
error_code = JANUS_VIDEOCALL_ERROR_MISSING_SDP; | |
g_snprintf(error_cause, 512, "Missing SDP"); | |
goto error; | |
} | |
char error_str[512]; | |
janus_sdp *offer = janus_sdp_parse(msg_sdp, error_str, sizeof(error_str)); | |
if(offer == NULL) { | |
if(g_atomic_int_compare_and_exchange(&session->incall, 1, 0) && peer) { | |
janus_refcount_decrease(&session->ref); | |
janus_refcount_decrease(&peer->ref); | |
} | |
janus_mutex_unlock(&sessions_mutex); | |
JANUS_LOG(LOG_ERR, "Error parsing offer: %s\n", error_str); | |
error_code = JANUS_VIDEOCALL_ERROR_INVALID_SDP; | |
g_snprintf(error_cause, 512, "Error parsing offer: %s", error_str); | |
goto error; | |
} | |
janus_sdp_destroy(offer); | |
g_atomic_int_set(&peer->incall, 1); | |
session->peer = peer; | |
peer->peer = session; | |
session->has_audio = (strstr(msg_sdp, "m=audio") != NULL); | |
session->has_video = (strstr(msg_sdp, "m=video") != NULL); | |
session->has_data = (strstr(msg_sdp, "DTLS/SCTP") != NULL); | |
janus_mutex_unlock(&sessions_mutex); | |
JANUS_LOG(LOG_VERB, "%s is calling %s\n", session->username, peer->username); | |
JANUS_LOG(LOG_VERB, "This is involving a negotiation (%s) as well:\n%s\n", msg_sdp_type, msg_sdp); | |
/* Check if this user will simulcast */ | |
json_t *msg_simulcast = json_object_get(msg->jsep, "simulcast"); | |
if(msg_simulcast) { | |
JANUS_LOG(LOG_VERB, "VideoCall caller (%s) is going to do simulcasting\n", session->username); | |
int rid_ext_id = -1, framemarking_ext_id = -1; | |
janus_rtp_simulcasting_prepare(msg_simulcast, &rid_ext_id, &framemarking_ext_id, session->ssrc, session->rid); | |
session->sim_context.rid_ext_id = rid_ext_id; | |
session->sim_context.framemarking_ext_id = framemarking_ext_id; | |
} | |
/* Send SDP to our peer */ | |
json_t *call = json_object(); | |
json_object_set_new(call, "videocall", json_string("event")); | |
json_t *calling = json_object(); | |
json_object_set_new(calling, "event", json_string("incomingcall")); | |
json_object_set_new(calling, "username", json_string(session->username)); | |
json_object_set_new(calling, "displayname", json_string(session->displayname)); | |
json_object_set_new(call, "result", calling); | |
json_t *jsep = json_pack("{ssss}", "type", msg_sdp_type, "sdp", msg_sdp); | |
if(session->e2ee) | |
json_object_set_new(jsep, "e2ee", json_true()); | |
g_atomic_int_set(&session->hangingup, 0); | |
int ret = gateway->push_event(peer->handle, &janus_videocall_plugin, NULL, call, jsep); | |
JANUS_LOG(LOG_VERB, " >> Pushing event to peer: %d (%s)\n", ret, janus_get_api_error(ret)); | |
json_decref(call); | |
json_decref(jsep); | |
/* Send an ack back */ | |
result = json_object(); | |
json_object_set_new(result, "event", json_string("calling")); | |
/* Also notify event handlers */ | |
if(notify_events && gateway->events_is_enabled()) { | |
json_t *info = json_object(); | |
json_object_set_new(info, "event", json_string("calling")); | |
gateway->notify_event(&janus_videocall_plugin, session->handle, info); | |
} | |
} | |
} else if(!strcasecmp(request_text, "accept")) { | |
/* Accept a call from another peer */ | |
janus_videocall_session *peer = session->peer; | |
if(peer == NULL || !g_atomic_int_get(&session->incall) || !g_atomic_int_get(&peer->incall)) { | |
JANUS_LOG(LOG_ERR, "No incoming call to accept\n"); | |
error_code = JANUS_VIDEOCALL_ERROR_NO_CALL; | |
g_snprintf(error_cause, 512, "No incoming call to accept"); | |
goto error; | |
} | |
janus_refcount_increase(&peer->ref); | |
/* Any SDP to handle? if not, something's wrong */ | |
if(!msg_sdp) { | |
janus_refcount_decrease(&peer->ref); | |
JANUS_LOG(LOG_ERR, "Missing SDP\n"); | |
error_code = JANUS_VIDEOCALL_ERROR_MISSING_SDP; | |
g_snprintf(error_cause, 512, "Missing SDP"); | |
goto error; | |
} | |
char error_str[512]; | |
janus_sdp *answer = janus_sdp_parse(msg_sdp, error_str, sizeof(error_str)); | |
if(answer == NULL) { | |
janus_refcount_decrease(&peer->ref); | |
JANUS_LOG(LOG_ERR, "Error parsing answer: %s\n", error_str); | |
error_code = JANUS_VIDEOCALL_ERROR_INVALID_SDP; | |
g_snprintf(error_cause, 512, "Error parsing answer: %s", error_str); | |
goto error; | |
} | |
JANUS_LOG(LOG_VERB, "%s is accepting a call from %s\n", session->username, peer->username); | |
JANUS_LOG(LOG_VERB, "This is involving a negotiation (%s) as well:\n%s\n", msg_sdp_type, msg_sdp); | |
session->has_audio = (strstr(msg_sdp, "m=audio") != NULL); | |
session->has_video = (strstr(msg_sdp, "m=video") != NULL); | |
session->has_data = (strstr(msg_sdp, "DTLS/SCTP") != NULL); | |
/* Check if this user will simulcast */ | |
json_t *msg_simulcast = json_object_get(msg->jsep, "simulcast"); | |
if(msg_simulcast && janus_get_codec_pt(msg_sdp, "vp8") > 0) { | |
JANUS_LOG(LOG_VERB, "VideoCall callee (%s) is going to do simulcasting\n", session->username); | |
session->ssrc[0] = json_integer_value(json_object_get(msg_simulcast, "ssrc-0")); | |
session->ssrc[1] = json_integer_value(json_object_get(msg_simulcast, "ssrc-1")); | |
session->ssrc[2] = json_integer_value(json_object_get(msg_simulcast, "ssrc-2")); | |
} else { | |
int i=0; | |
for(i=0; i<3; i++) { | |
session->ssrc[i] = 0; | |
g_free(session->rid[0]); | |
session->rid[0] = NULL; | |
if(peer) { | |
peer->ssrc[i] = 0; | |
g_free(peer->rid[0]); | |
peer->rid[0] = NULL; | |
} | |
} | |
} | |
/* Check which codecs we ended up using */ | |
const char *acodec = NULL, *vcodec = NULL; | |
janus_sdp_find_first_codecs(answer, &acodec, &vcodec); | |
session->acodec = janus_audiocodec_from_name(acodec); | |
session->vcodec = janus_videocodec_from_name(vcodec); | |
if(session->acodec == JANUS_AUDIOCODEC_NONE) { | |
session->has_audio = FALSE; | |
if(peer) | |
peer->has_audio = FALSE; | |
} else if(peer) { | |
peer->acodec = session->acodec; | |
} | |
if(session->vcodec == JANUS_VIDEOCODEC_NONE) { | |
session->has_video = FALSE; | |
if(peer) | |
peer->has_video = FALSE; | |
} else if(peer) { | |
peer->vcodec = session->vcodec; | |
} | |
janus_sdp_destroy(answer); | |
/* Send SDP to our peer */ | |
json_t *jsep = json_pack("{ssss}", "type", msg_sdp_type, "sdp", msg_sdp); | |
if(session->e2ee) | |
json_object_set_new(jsep, "e2ee", json_true()); | |
json_t *call = json_object(); | |
json_object_set_new(call, "videocall", json_string("event")); | |
json_t *calling = json_object(); | |
json_object_set_new(calling, "event", json_string("accepted")); | |
json_object_set_new(calling, "username", json_string(session->username)); | |
json_object_set_new(call, "result", calling); | |
g_atomic_int_set(&session->hangingup, 0); | |
int ret = gateway->push_event(peer->handle, &janus_videocall_plugin, NULL, call, jsep); | |
JANUS_LOG(LOG_VERB, " >> Pushing event to peer: %d (%s)\n", ret, janus_get_api_error(ret)); | |
json_decref(call); | |
json_decref(jsep); | |
/* Send an ack back */ | |
result = json_object(); | |
json_object_set_new(result, "event", json_string("accepted")); | |
/* Also notify event handlers */ | |
if(notify_events && gateway->events_is_enabled()) { | |
json_t *info = json_object(); | |
json_object_set_new(info, "event", json_string("accepted")); | |
gateway->notify_event(&janus_videocall_plugin, session->handle, info); | |
} | |
/* Is simulcasting involved on either side? */ | |
if(session->ssrc[0] || session->rid[0]) { | |
peer->sim_context.substream_target = 2; /* Let's aim for the highest quality */ | |
peer->sim_context.templayer_target = 2; /* Let's aim for all temporal layers */ | |
} | |
if(peer->ssrc[0] || peer->rid[0]) { | |
session->sim_context.substream_target = 2; /* Let's aim for the highest quality */ | |
session->sim_context.templayer_target = 2; /* Let's aim for all temporal layers */ | |
} | |
/* We don't need this reference anymore, it was already increased by the peer calling us */ | |
janus_refcount_decrease(&peer->ref); | |
} else if(!strcasecmp(request_text, "set")) { | |
/* Update the local configuration (audio/video mute/unmute, bitrate cap or recording) */ | |
JANUS_VALIDATE_JSON_OBJECT(root, set_parameters, | |
error_code, error_cause, TRUE, | |
JANUS_VIDEOCALL_ERROR_MISSING_ELEMENT, JANUS_VIDEOCALL_ERROR_INVALID_ELEMENT); | |
if(error_code != 0) | |
goto error; | |
json_t *audio = json_object_get(root, "audio"); | |
json_t *video = json_object_get(root, "video"); | |
json_t *bitrate = json_object_get(root, "bitrate"); | |
json_t *record = json_object_get(root, "record"); | |
json_t *recfile = json_object_get(root, "filename"); | |
json_t *restart = json_object_get(root, "restart"); | |
json_t *substream = json_object_get(root, "substream"); | |
if(substream && (!json_is_integer(substream) || json_integer_value(substream) < 0 || json_integer_value(substream) > 2)) { | |
JANUS_LOG(LOG_ERR, "Invalid element (substream should be 0, 1 or 2)\n"); | |
error_code = JANUS_VIDEOCALL_ERROR_INVALID_ELEMENT; | |
g_snprintf(error_cause, 512, "Invalid value (substream should be 0, 1 or 2)"); | |
goto error; | |
} | |
json_t *temporal = json_object_get(root, "temporal"); | |
if(temporal && (!json_is_integer(temporal) || json_integer_value(temporal) < 0 || json_integer_value(temporal) > 2)) { | |
JANUS_LOG(LOG_ERR, "Invalid element (temporal should be 0, 1 or 2)\n"); | |
error_code = JANUS_VIDEOCALL_ERROR_INVALID_ELEMENT; | |
g_snprintf(error_cause, 512, "Invalid value (temporal should be 0, 1 or 2)"); | |
goto error; | |
} | |
json_t *fallback = json_object_get(root, "fallback"); | |
if(fallback && (!json_is_integer(fallback) || json_integer_value(fallback) < 0)) { | |
JANUS_LOG(LOG_ERR, "Invalid element (fallback should be a positive integer)\n"); | |
error_code = JANUS_VIDEOCALL_ERROR_INVALID_ELEMENT; | |
g_snprintf(error_cause, 512, "Invalid value (fallback should be a positive integer)"); | |
goto error; | |
} | |
if(audio) { | |
session->audio_active = json_is_true(audio); | |
JANUS_LOG(LOG_VERB, "Setting audio property: %s\n", session->audio_active ? "true" : "false"); | |
} | |
if(video) { | |
if(!session->video_active && json_is_true(video)) { | |
/* Send a PLI */ | |
JANUS_LOG(LOG_VERB, "Just (re-)enabled video, sending a PLI to recover it\n"); | |
gateway->send_pli(session->handle); | |
} | |
session->video_active = json_is_true(video); | |
JANUS_LOG(LOG_VERB, "Setting video property: %s\n", session->video_active ? "true" : "false"); | |
} | |
if(bitrate) { | |
session->bitrate = json_integer_value(bitrate); | |
JANUS_LOG(LOG_VERB, "Setting video bitrate: %"SCNu32"\n", session->bitrate); | |
gateway->send_remb(session->handle, session->bitrate ? session->bitrate : 10000000); | |
} | |
janus_videocall_session *peer = session->peer; | |
if(fallback) { | |
JANUS_LOG(LOG_VERB, "Setting fallback timer (simulcast): %lld (was %"SCNu32")\n", | |
json_integer_value(fallback) ? json_integer_value(fallback) : 250000, | |
session->sim_context.drop_trigger ? session->sim_context.drop_trigger : 250000); | |
session->sim_context.drop_trigger = json_integer_value(fallback); | |
} | |
if(substream) { | |
session->sim_context.substream_target = json_integer_value(substream); | |
JANUS_LOG(LOG_VERB, "Setting video SSRC to let through (simulcast): %"SCNu32" (index %d, was %d)\n", | |
session->ssrc[session->sim_context.substream], session->sim_context.substream_target, session->sim_context.substream); | |
if(session->sim_context.substream_target == session->sim_context.substream) { | |
/* No need to do anything, we're already getting the right substream, so notify the user */ | |
json_t *event = json_object(); | |
json_object_set_new(event, "videocall", json_string("event")); | |
json_t *result = json_object(); | |
json_object_set_new(result, "event", json_string("simulcast")); | |
json_object_set_new(result, "videocodec", json_string(janus_videocodec_name(session->vcodec))); | |
json_object_set_new(result, "substream", json_integer(session->sim_context.substream)); | |
json_object_set_new(event, "result", result); | |
gateway->push_event(session->handle, &janus_videocall_plugin, NULL, event, NULL); | |
json_decref(event); | |
} else { | |
/* We need to change substream, send the peer a PLI */ | |
JANUS_LOG(LOG_VERB, "Simulcasting substream change, sending a PLI to kickstart it\n"); | |
if(peer && peer->handle) | |
gateway->send_pli(peer->handle); | |
} | |
} | |
if(temporal) { | |
session->sim_context.templayer_target = json_integer_value(temporal); | |
JANUS_LOG(LOG_VERB, "Setting video temporal layer to let through (simulcast): %d (was %d)\n", | |
session->sim_context.templayer_target, session->sim_context.templayer); | |
if(session->vcodec == JANUS_VIDEOCODEC_VP8 && session->sim_context.templayer_target == session->sim_context.templayer) { | |
/* No need to do anything, we're already getting the right temporal, so notify the user */ | |
json_t *event = json_object(); | |
json_object_set_new(event, "videocall", json_string("event")); | |
json_t *result = json_object(); | |
json_object_set_new(result, "event", json_string("simulcast")); | |
json_object_set_new(result, "videocodec", json_string(janus_videocodec_name(session->vcodec))); | |
json_object_set_new(result, "temporal", json_integer(session->sim_context.templayer)); | |
json_object_set_new(event, "result", result); | |
gateway->push_event(session->handle, &janus_videocall_plugin, NULL, event, NULL); | |
json_decref(event); | |
} else { | |
/* We need to change temporal, send a PLI */ | |
JANUS_LOG(LOG_VERB, "Simulcasting temporal layer change, sending a PLI to kickstart it\n"); | |
if(peer && peer->handle) | |
gateway->send_pli(peer->handle); | |
} | |
} | |
if(record) { | |
if(msg_sdp) { | |
session->has_audio = (strstr(msg_sdp, "m=audio") != NULL); | |
session->has_video = (strstr(msg_sdp, "m=video") != NULL); | |
session->has_data = (strstr(msg_sdp, "DTLS/SCTP") != NULL); | |
} | |
gboolean recording = json_is_true(record); | |
const char *recording_base = json_string_value(recfile); | |
JANUS_LOG(LOG_VERB, "Recording %s (base filename: %s)\n", recording ? "enabled" : "disabled", recording_base ? recording_base : "not provided"); | |
janus_mutex_lock(&session->rec_mutex); | |
if(!recording) { | |
/* Not recording (anymore?) */ | |
janus_videocall_recorder_close(session); | |
} else { | |
/* We've started recording, send a PLI and go on */ | |
char filename[255]; | |
gint64 now = janus_get_real_time(); | |
if(session->has_audio) { | |
/* Prepare an audio recording */ | |
janus_recorder *rc = NULL; | |
memset(filename, 0, 255); | |
if(recording_base) { | |
/* Use the filename and path we have been provided */ | |
g_snprintf(filename, 255, "%s-audio", recording_base); | |
session->arc = janus_recorder_create(NULL, janus_audiocodec_name(session->acodec), filename); | |
if(session->arc == NULL) { | |
/* FIXME We should notify the fact the recorder could not be created */ | |
JANUS_LOG(LOG_ERR, "Couldn't open an audio recording file for this VideoCall user!\n"); | |
} | |
} else { | |
/* Build a filename */ | |
g_snprintf(filename, 255, "videocall-%s-%s-%"SCNi64"-audio", | |
session->username ? session->username : "unknown", | |
(peer && peer->username) ? peer->username : "unknown", | |
now); | |
session->arc = janus_recorder_create(NULL, janus_audiocodec_name(session->acodec), filename); | |
if(session->arc == NULL) { | |
/* FIXME We should notify the fact the recorder could not be created */ | |
JANUS_LOG(LOG_ERR, "Couldn't open an audio recording file for this VideoCall user!\n"); | |
} | |
} | |
/* If media is encrypted, mark it in the recording */ | |
if(session->e2ee) | |
janus_recorder_encrypted(rc); | |
session->arc = rc; | |
} | |
if(session->has_video) { | |
/* Prepare a video recording */ | |
janus_recorder *rc = NULL; | |
memset(filename, 0, 255); | |
if(recording_base) { | |
/* Use the filename and path we have been provided */ | |
g_snprintf(filename, 255, "%s-video", recording_base); | |
session->vrc = janus_recorder_create(NULL, janus_videocodec_name(session->vcodec), filename); | |
if(session->vrc == NULL) { | |
/* FIXME We should notify the fact the recorder could not be created */ | |
JANUS_LOG(LOG_ERR, "Couldn't open an video recording file for this VideoCall user!\n"); | |
} | |
} else { | |
/* Build a filename */ | |
g_snprintf(filename, 255, "videocall-%s-%s-%"SCNi64"-video", | |
session->username ? session->username : "unknown", | |
(peer && peer->username) ? peer->username : "unknown", | |
now); | |
session->vrc = janus_recorder_create(NULL, janus_videocodec_name(session->vcodec), filename); | |
if(session->vrc == NULL) { | |
/* FIXME We should notify the fact the recorder could not be created */ | |
JANUS_LOG(LOG_ERR, "Couldn't open an video recording file for this VideoCall user!\n"); | |
} | |
} | |
/* Send a PLI */ | |
JANUS_LOG(LOG_VERB, "Recording video, sending a PLI to kickstart it\n"); | |
gateway->send_pli(session->handle); | |
/* If media is encrypted, mark it in the recording */ | |
if(session->e2ee) | |
janus_recorder_encrypted(rc); | |
session->vrc = rc; | |
} | |
if(session->has_data) { | |
/* Prepare a data recording */ | |
janus_recorder *rc = NULL; | |
memset(filename, 0, 255); | |
if(recording_base) { | |
/* Use the filename and path we have been provided */ | |
g_snprintf(filename, 255, "%s-data", recording_base); | |
rc = janus_recorder_create(NULL, "text", filename); | |
if(rc == NULL) { | |
/* FIXME We should notify the fact the recorder could not be created */ | |
JANUS_LOG(LOG_ERR, "Couldn't open a data recording file for this VideoCall user!\n"); | |
} | |
} else { | |
/* Build a filename */ | |
g_snprintf(filename, 255, "videocall-%s-%s-%"SCNi64"-data", | |
session->username ? session->username : "unknown", | |
(peer && peer->username) ? peer->username : "unknown", | |
now); | |
rc = janus_recorder_create(NULL, "text", filename); | |
if(rc == NULL) { | |
/* FIXME We should notify the fact the recorder could not be created */ | |
JANUS_LOG(LOG_ERR, "Couldn't open a data recording file for this VideoCall user!\n"); | |
} | |
} | |
/* Media encryption doesn't apply to data channels */ | |
session->drc = rc; | |
} | |
} | |
janus_mutex_unlock(&session->rec_mutex); | |
} | |
/* Also notify event handlers */ | |
if(notify_events && gateway->events_is_enabled()) { | |
json_t *info = json_object(); | |
json_object_set_new(info, "event", json_string("configured")); | |
json_object_set_new(info, "audio_active", session->audio_active ? json_true() : json_false()); | |
json_object_set_new(info, "video_active", session->video_active ? json_true() : json_false()); | |
json_object_set_new(info, "bitrate", json_integer(session->bitrate)); | |
if(session->arc || session->vrc || session->drc) { | |
json_t *recording = json_object(); | |
if(session->arc && session->arc->filename) | |
json_object_set_new(recording, "audio", json_string(session->arc->filename)); | |
if(session->vrc && session->vrc->filename) | |
json_object_set_new(recording, "video", json_string(session->vrc->filename)); | |
if(session->drc && session->drc->filename) | |
json_object_set_new(recording, "data", json_string(session->drc->filename)); | |
json_object_set_new(info, "recording", recording); | |
} | |
gateway->notify_event(&janus_videocall_plugin, session->handle, info); | |
} | |
/* Send an ack back */ | |
result = json_object(); | |
json_object_set_new(result, "event", json_string("set")); | |
/* If this is for an ICE restart, prepare the SDP to send back too */ | |
gboolean do_restart = restart ? json_is_true(restart) : FALSE; | |
if(do_restart && !sdp_update) { | |
JANUS_LOG(LOG_WARN, "Got a 'restart' request, but no SDP update? Ignoring...\n"); | |
} | |
if(sdp_update && peer != NULL) { | |
/* Forward new SDP to the peer */ | |
json_t *event = json_object(); | |
json_object_set_new(event, "videocall", json_string("event")); | |
json_t *update = json_object(); | |
json_object_set_new(update, "event", json_string("update")); | |
json_object_set_new(event, "result", update); | |
json_t *jsep = json_pack("{ssss}", "type", msg_sdp_type, "sdp", msg_sdp); | |
int ret = gateway->push_event(peer->handle, &janus_videocall_plugin, NULL, event, jsep); | |
JANUS_LOG(LOG_VERB, " >> Pushing event: %d (%s)\n", ret, janus_get_api_error(ret)); | |
json_decref(event); | |
json_decref(jsep); | |
} | |
} else if(!strcasecmp(request_text, "hangup")) { | |
json_t *hangup = json_object_get(root, "reason"); | |
if(hangup && !json_is_string(hangup)) { | |
JANUS_LOG(LOG_WARN, "Invalid element (hangup should be a string), ignoring\n"); | |
hangup = NULL; | |
} | |
const char *hangup_text = hangup ? json_string_value(hangup) : "We did the hangup"; | |
/* Hangup an ongoing call or reject an incoming one */ | |
janus_videocall_session *peer = session->peer; | |
if(peer == NULL) { | |
JANUS_LOG(LOG_WARN, "No call to hangup\n"); | |
} else { | |
JANUS_LOG(LOG_VERB, "%s is hanging up the call with %s (%s)\n", session->username, peer->username, hangup_text); | |
} | |
/* Check if we still need to remove any reference */ | |
if(peer && g_atomic_int_compare_and_exchange(&peer->incall, 1, 0)) { | |
janus_refcount_decrease(&session->ref); | |
} | |
if(g_atomic_int_compare_and_exchange(&session->incall, 1, 0) && peer) { | |
janus_refcount_decrease(&peer->ref); | |
} | |
/* Notify the success as an hangup message */ | |
result = json_object(); | |
json_object_set_new(result, "event", json_string("hangup")); | |
json_object_set_new(result, "username", json_string(session->username)); | |
json_object_set_new(result, "reason", json_string(hangup_text)); | |
json_object_set_new(result, "reason", json_string("Explicit hangup")); | |
/* Also notify event handlers */ | |
if(notify_events && gateway->events_is_enabled()) { | |
json_t *info = json_object(); | |
json_object_set_new(info, "event", json_string("hangup")); | |
json_object_set_new(info, "reason", json_string("Explicit hangup")); | |
gateway->notify_event(&janus_videocall_plugin, session->handle, info); | |
} | |
/* Hangup the call on the user, if it's still up */ | |
gateway->close_pc(session->handle); | |
if(peer != NULL) { | |
/* Send event to our peer too */ | |
json_t *call = json_object(); | |
json_object_set_new(call, "videocall", json_string("event")); | |
json_t *calling = json_object(); | |
json_object_set_new(calling, "event", json_string("hangup")); | |
json_object_set_new(calling, "username", json_string(session->username)); | |
json_object_set_new(calling, "reason", json_string(hangup_text)); | |
json_object_set_new(call, "result", calling); | |
gateway->close_pc(peer->handle); | |
int ret = gateway->push_event(peer->handle, &janus_videocall_plugin, NULL, call, NULL); | |
JANUS_LOG(LOG_VERB, " >> Pushing event to peer: %d (%s)\n", ret, janus_get_api_error(ret)); | |
json_decref(call); | |
/* Also notify event handlers */ | |
if(notify_events && gateway->events_is_enabled()) { | |
json_t *info = json_object(); | |
json_object_set_new(info, "event", json_string("hangup")); | |
json_object_set_new(info, "reason", json_string("Remote hangup")); | |
gateway->notify_event(&janus_videocall_plugin, peer->handle, info); | |
} | |
/* Hangup the call on the peer, if it's still up */ | |
gateway->close_pc(peer->handle); | |
} | |
} else { | |
JANUS_LOG(LOG_ERR, "Unknown request (%s)\n", request_text); | |
error_code = JANUS_VIDEOCALL_ERROR_INVALID_REQUEST; | |
g_snprintf(error_cause, 512, "Unknown request (%s)", request_text); | |
goto error; | |
} | |
/* Prepare JSON event */ | |
json_t *event = json_object(); | |
json_object_set_new(event, "videocall", json_string("event")); | |
if(result != NULL) | |
json_object_set_new(event, "result", result); | |
int ret = gateway->push_event(msg->handle, &janus_videocall_plugin, msg->transaction, event, NULL); | |
JANUS_LOG(LOG_VERB, " >> Pushing event: %d (%s)\n", ret, janus_get_api_error(ret)); | |
json_decref(event); | |
janus_videocall_message_free(msg); | |
continue; | |
error: | |
{ | |
/* Prepare JSON error event */ | |
json_t *event = json_object(); | |
json_object_set_new(event, "videocall", json_string("event")); | |
json_object_set_new(event, "error_code", json_integer(error_code)); | |
json_object_set_new(event, "error", json_string(error_cause)); | |
int ret = gateway->push_event(msg->handle, &janus_videocall_plugin, msg->transaction, event, NULL); | |
JANUS_LOG(LOG_VERB, " >> Pushing event: %d (%s)\n", ret, janus_get_api_error(ret)); | |
json_decref(event); | |
janus_videocall_message_free(msg); | |
} | |
} | |
JANUS_LOG(LOG_VERB, "Leaving VideoCall handler thread\n"); | |
return NULL; | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment