Statistics
| Branch: | Revision:

janus-gateway / plugins / janus_echotest.c @ 0351dafc

History | View | Annotate | Download (36.4 KB)

1
/*! \file   janus_echotest.c
2
 * \author Lorenzo Miniero <lorenzo@meetecho.com>
3
 * \copyright GNU General Public License v3
4
 * \brief  Janus EchoTest plugin
5
 * \details  This is a trivial EchoTest plugin for Janus, just used to
6
 * showcase the plugin interface. A peer attaching to this plugin will
7
 * receive back the same RTP packets and RTCP messages he sends: the
8
 * RTCP messages, of course, would be modified on the way by the gateway
9
 * to make sure they are coherent with the involved SSRCs. In order to
10
 * demonstrate how peer-provided messages can change the behaviour of a
11
 * plugin, this plugin implements a simple API based on three messages:
12
 * 
13
 * 1. a message to enable/disable audio (that is, to tell the plugin
14
 * whether incoming audio RTP packets need to be sent back or discarded);
15
 * 2. a message to enable/disable video (that is, to tell the plugin
16
 * whether incoming video RTP packets need to be sent back or discarded);
17
 * 3. a message to cap the bitrate (which would modify incoming RTCP
18
 * REMB messages before sending them back, in order to trick the peer into
19
 * thinking the available bandwidth is different).
20
 * 
21
 * \section echoapi Echo Test API
22
 * 
23
 * There's a single unnamed request you can send and it's asynchronous,
24
 * which means all responses (successes and errors) will be delivered
25
 * as events with the same transaction. 
26
 * 
27
 * The request has to be formatted as follows. All the attributes are
28
 * optional, so any request can contain a subset of them:
29
 *
30
\verbatim
31
{
32
        "audio" : true|false,
33
        "video" : true|false,
34
        "bitrate" : <numeric bitrate value>,
35
        "record" : true|false,
36
        "filename" : <base path/filename to use for the recording>
37
}
38
\endverbatim
39
 *
40
 * \c audio instructs the plugin to do or do not bounce back audio
41
 * frames; \c video does the same for video; \c bitrate caps the
42
 * bandwidth to force on the browser encoding side (e.g., 128000 for
43
 * 128kbps).
44
 * 
45
 * The first request must be sent together with a JSEP offer to
46
 * negotiate a PeerConnection: a JSEP answer will be provided with
47
 * the asynchronous response notification. Subsequent requests (e.g., to
48
 * dynamically manipulate the bitrate while testing) have to be sent
49
 * without any JSEP payload attached.
50
 * 
51
 * A successful request will result in an \c ok event:
52
 * 
53
\verbatim
54
{
55
        "echotest" : "event",
56
        "result": "ok"
57
}
58
\endverbatim
59
 * 
60
 * An error instead will provide both an error code and a more verbose
61
 * description of the cause of the issue:
62
 * 
63
\verbatim
64
{
65
        "echotest" : "event",
66
        "error_code" : <numeric ID, check Macros below>,
67
        "error" : "<error description as a string>"
68
}
69
\endverbatim
70
 *
71
 * If the plugin detects a loss of the associated PeerConnection, a
72
 * "done" notification is triggered to inform the application the Echo
73
 * Test session is over:
74
 * 
75
\verbatim
76
{
77
        "echotest" : "event",
78
        "result": "done"
79
}
80
\endverbatim
81
 *
82
 * \ingroup plugins
83
 * \ref plugins
84
 */
85

    
86
#include "plugin.h"
87

    
88
#include <jansson.h>
89

    
90
#include "../debug.h"
91
#include "../apierror.h"
92
#include "../config.h"
93
#include "../mutex.h"
94
#include "../record.h"
95
#include "../rtcp.h"
96
#include "../utils.h"
97

    
98

    
99
/* Plugin information */
100
#define JANUS_ECHOTEST_VERSION                        6
101
#define JANUS_ECHOTEST_VERSION_STRING        "0.0.6"
102
#define JANUS_ECHOTEST_DESCRIPTION                "This is a trivial EchoTest plugin for Janus, just used to showcase the plugin interface."
103
#define JANUS_ECHOTEST_NAME                                "JANUS EchoTest plugin"
104
#define JANUS_ECHOTEST_AUTHOR                        "Meetecho s.r.l."
105
#define JANUS_ECHOTEST_PACKAGE                        "janus.plugin.echotest"
106

    
107
/* Plugin methods */
108
janus_plugin *create(void);
109
int janus_echotest_init(janus_callbacks *callback, const char *config_path);
110
void janus_echotest_destroy(void);
111
int janus_echotest_get_api_compatibility(void);
112
int janus_echotest_get_version(void);
113
const char *janus_echotest_get_version_string(void);
114
const char *janus_echotest_get_description(void);
115
const char *janus_echotest_get_name(void);
116
const char *janus_echotest_get_author(void);
117
const char *janus_echotest_get_package(void);
118
void janus_echotest_create_session(janus_plugin_session *handle, int *error);
119
struct janus_plugin_result *janus_echotest_handle_message(janus_plugin_session *handle, char *transaction, char *message, char *sdp_type, char *sdp);
120
void janus_echotest_setup_media(janus_plugin_session *handle);
121
void janus_echotest_incoming_rtp(janus_plugin_session *handle, int video, char *buf, int len);
122
void janus_echotest_incoming_rtcp(janus_plugin_session *handle, int video, char *buf, int len);
123
void janus_echotest_incoming_data(janus_plugin_session *handle, char *buf, int len);
124
void janus_echotest_slow_link(janus_plugin_session *handle, int uplink, int video);
125
void janus_echotest_hangup_media(janus_plugin_session *handle);
126
void janus_echotest_destroy_session(janus_plugin_session *handle, int *error);
127
char *janus_echotest_query_session(janus_plugin_session *handle);
128

    
129
/* Plugin setup */
130
static janus_plugin janus_echotest_plugin =
131
        JANUS_PLUGIN_INIT (
132
                .init = janus_echotest_init,
133
                .destroy = janus_echotest_destroy,
134

    
135
                .get_api_compatibility = janus_echotest_get_api_compatibility,
136
                .get_version = janus_echotest_get_version,
137
                .get_version_string = janus_echotest_get_version_string,
138
                .get_description = janus_echotest_get_description,
139
                .get_name = janus_echotest_get_name,
140
                .get_author = janus_echotest_get_author,
141
                .get_package = janus_echotest_get_package,
142
                
143
                .create_session = janus_echotest_create_session,
144
                .handle_message = janus_echotest_handle_message,
145
                .setup_media = janus_echotest_setup_media,
146
                .incoming_rtp = janus_echotest_incoming_rtp,
147
                .incoming_rtcp = janus_echotest_incoming_rtcp,
148
                .incoming_data = janus_echotest_incoming_data,
149
                .slow_link = janus_echotest_slow_link,
150
                .hangup_media = janus_echotest_hangup_media,
151
                .destroy_session = janus_echotest_destroy_session,
152
                .query_session = janus_echotest_query_session,
153
        );
154

    
155
/* Plugin creator */
156
janus_plugin *create(void) {
157
        JANUS_LOG(LOG_VERB, "%s created!\n", JANUS_ECHOTEST_NAME);
158
        return &janus_echotest_plugin;
159
}
160

    
161

    
162
/* Useful stuff */
163
static volatile gint initialized = 0, stopping = 0;
164
static janus_callbacks *gateway = NULL;
165
static GThread *handler_thread;
166
static GThread *watchdog;
167
static void *janus_echotest_handler(void *data);
168

    
169
typedef struct janus_echotest_message {
170
        janus_plugin_session *handle;
171
        char *transaction;
172
        char *message;
173
        char *sdp_type;
174
        char *sdp;
175
} janus_echotest_message;
176
static GAsyncQueue *messages = NULL;
177
static janus_echotest_message exit_message;
178

    
179
typedef struct janus_echotest_session {
180
        janus_plugin_session *handle;
181
        gboolean has_audio;
182
        gboolean has_video;
183
        gboolean has_data;
184
        gboolean audio_active;
185
        gboolean video_active;
186
        uint64_t bitrate;
187
        janus_recorder *arc;        /* The Janus recorder instance for this user's audio, if enabled */
188
        janus_recorder *vrc;        /* The Janus recorder instance for this user's video, if enabled */
189
        janus_recorder *drc;        /* The Janus recorder instance for this user's data, if enabled */
190
        janus_mutex rec_mutex;        /* Mutex to protect the recorders from race conditions */
191
        guint16 slowlink_count;
192
        volatile gint hangingup;
193
        gint64 destroyed;        /* Time at which this session was marked as destroyed */
194
} janus_echotest_session;
195
static GHashTable *sessions;
196
static GList *old_sessions;
197
static janus_mutex sessions_mutex;
198

    
199
static void janus_echotest_message_free(janus_echotest_message *msg) {
200
        if(!msg || msg == &exit_message)
201
                return;
202

    
203
        msg->handle = NULL;
204

    
205
        g_free(msg->transaction);
206
        msg->transaction = NULL;
207
        g_free(msg->message);
208
        msg->message = NULL;
209
        g_free(msg->sdp_type);
210
        msg->sdp_type = NULL;
211
        g_free(msg->sdp);
212
        msg->sdp = NULL;
213

    
214
        g_free(msg);
215
}
216

    
217

    
218
/* Error codes */
219
#define JANUS_ECHOTEST_ERROR_NO_MESSAGE                        411
220
#define JANUS_ECHOTEST_ERROR_INVALID_JSON                412
221
#define JANUS_ECHOTEST_ERROR_INVALID_ELEMENT        413
222

    
223

    
224
/* EchoTest watchdog/garbage collector (sort of) */
225
void *janus_echotest_watchdog(void *data);
226
void *janus_echotest_watchdog(void *data) {
227
        JANUS_LOG(LOG_INFO, "EchoTest watchdog started\n");
228
        gint64 now = 0;
229
        while(g_atomic_int_get(&initialized) && !g_atomic_int_get(&stopping)) {
230
                janus_mutex_lock(&sessions_mutex);
231
                /* Iterate on all the sessions */
232
                now = janus_get_monotonic_time();
233
                if(old_sessions != NULL) {
234
                        GList *sl = old_sessions;
235
                        JANUS_LOG(LOG_HUGE, "Checking %d old EchoTest sessions...\n", g_list_length(old_sessions));
236
                        while(sl) {
237
                                janus_echotest_session *session = (janus_echotest_session *)sl->data;
238
                                if(!session) {
239
                                        sl = sl->next;
240
                                        continue;
241
                                }
242
                                if(now-session->destroyed >= 5*G_USEC_PER_SEC) {
243
                                        /* We're lazy and actually get rid of the stuff only after a few seconds */
244
                                        JANUS_LOG(LOG_VERB, "Freeing old EchoTest session\n");
245
                                        GList *rm = sl->next;
246
                                        old_sessions = g_list_delete_link(old_sessions, sl);
247
                                        sl = rm;
248
                                        session->handle = NULL;
249
                                        g_free(session);
250
                                        session = NULL;
251
                                        continue;
252
                                }
253
                                sl = sl->next;
254
                        }
255
                }
256
                janus_mutex_unlock(&sessions_mutex);
257
                g_usleep(500000);
258
        }
259
        JANUS_LOG(LOG_INFO, "EchoTest watchdog stopped\n");
260
        return NULL;
261
}
262

    
263

    
264
/* Plugin implementation */
265
int janus_echotest_init(janus_callbacks *callback, const char *config_path) {
266
        if(g_atomic_int_get(&stopping)) {
267
                /* Still stopping from before */
268
                return -1;
269
        }
270
        if(callback == NULL || config_path == NULL) {
271
                /* Invalid arguments */
272
                return -1;
273
        }
274

    
275
        /* Read configuration */
276
        char filename[255];
277
        g_snprintf(filename, 255, "%s/%s.cfg", config_path, JANUS_ECHOTEST_PACKAGE);
278
        JANUS_LOG(LOG_VERB, "Configuration file: %s\n", filename);
279
        janus_config *config = janus_config_parse(filename);
280
        if(config != NULL)
281
                janus_config_print(config);
282
        /* This plugin actually has nothing to configure... */
283
        janus_config_destroy(config);
284
        config = NULL;
285
        
286
        sessions = g_hash_table_new(NULL, NULL);
287
        janus_mutex_init(&sessions_mutex);
288
        messages = g_async_queue_new_full((GDestroyNotify) janus_echotest_message_free);
289
        /* This is the callback we'll need to invoke to contact the gateway */
290
        gateway = callback;
291
        g_atomic_int_set(&initialized, 1);
292

    
293
        GError *error = NULL;
294
        /* Start the sessions watchdog */
295
        watchdog = g_thread_try_new("etest watchdog", &janus_echotest_watchdog, NULL, &error);
296
        if(error != NULL) {
297
                g_atomic_int_set(&initialized, 0);
298
                JANUS_LOG(LOG_ERR, "Got error %d (%s) trying to launch the EchoTest watchdog thread...\n", error->code, error->message ? error->message : "??");
299
                return -1;
300
        }
301
        /* Launch the thread that will handle incoming messages */
302
        handler_thread = g_thread_try_new("janus echotest handler", janus_echotest_handler, NULL, &error);
303
        if(error != NULL) {
304
                g_atomic_int_set(&initialized, 0);
305
                JANUS_LOG(LOG_ERR, "Got error %d (%s) trying to launch the EchoTest handler thread...\n", error->code, error->message ? error->message : "??");
306
                return -1;
307
        }
308
        JANUS_LOG(LOG_INFO, "%s initialized!\n", JANUS_ECHOTEST_NAME);
309
        return 0;
310
}
311

    
312
void janus_echotest_destroy(void) {
313
        if(!g_atomic_int_get(&initialized))
314
                return;
315
        g_atomic_int_set(&stopping, 1);
316

    
317
        g_async_queue_push(messages, &exit_message);
318
        if(handler_thread != NULL) {
319
                g_thread_join(handler_thread);
320
                handler_thread = NULL;
321
        }
322
        if(watchdog != NULL) {
323
                g_thread_join(watchdog);
324
                watchdog = NULL;
325
        }
326

    
327
        /* FIXME We should destroy the sessions cleanly */
328
        janus_mutex_lock(&sessions_mutex);
329
        g_hash_table_destroy(sessions);
330
        janus_mutex_unlock(&sessions_mutex);
331
        g_async_queue_unref(messages);
332
        messages = NULL;
333
        sessions = NULL;
334

    
335
        g_atomic_int_set(&initialized, 0);
336
        g_atomic_int_set(&stopping, 0);
337
        JANUS_LOG(LOG_INFO, "%s destroyed!\n", JANUS_ECHOTEST_NAME);
338
}
339

    
340
int janus_echotest_get_api_compatibility(void) {
341
        /* Important! This is what your plugin MUST always return: don't lie here or bad things will happen */
342
        return JANUS_PLUGIN_API_VERSION;
343
}
344

    
345
int janus_echotest_get_version(void) {
346
        return JANUS_ECHOTEST_VERSION;
347
}
348

    
349
const char *janus_echotest_get_version_string(void) {
350
        return JANUS_ECHOTEST_VERSION_STRING;
351
}
352

    
353
const char *janus_echotest_get_description(void) {
354
        return JANUS_ECHOTEST_DESCRIPTION;
355
}
356

    
357
const char *janus_echotest_get_name(void) {
358
        return JANUS_ECHOTEST_NAME;
359
}
360

    
361
const char *janus_echotest_get_author(void) {
362
        return JANUS_ECHOTEST_AUTHOR;
363
}
364

    
365
const char *janus_echotest_get_package(void) {
366
        return JANUS_ECHOTEST_PACKAGE;
367
}
368

    
369
void janus_echotest_create_session(janus_plugin_session *handle, int *error) {
370
        if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized)) {
371
                *error = -1;
372
                return;
373
        }        
374
        janus_echotest_session *session = (janus_echotest_session *)g_malloc0(sizeof(janus_echotest_session));
375
        if(session == NULL) {
376
                JANUS_LOG(LOG_FATAL, "Memory error!\n");
377
                *error = -2;
378
                return;
379
        }
380
        session->handle = handle;
381
        session->has_audio = FALSE;
382
        session->has_video = FALSE;
383
        session->has_data = FALSE;
384
        session->audio_active = TRUE;
385
        session->video_active = TRUE;
386
        janus_mutex_init(&session->rec_mutex);
387
        session->bitrate = 0;        /* No limit */
388
        session->destroyed = 0;
389
        g_atomic_int_set(&session->hangingup, 0);
390
        handle->plugin_handle = session;
391
        janus_mutex_lock(&sessions_mutex);
392
        g_hash_table_insert(sessions, handle, session);
393
        janus_mutex_unlock(&sessions_mutex);
394

    
395
        return;
396
}
397

    
398
void janus_echotest_destroy_session(janus_plugin_session *handle, int *error) {
399
        if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized)) {
400
                *error = -1;
401
                return;
402
        }        
403
        janus_echotest_session *session = (janus_echotest_session *)handle->plugin_handle;
404
        if(!session) {
405
                JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
406
                *error = -2;
407
                return;
408
        }
409
        JANUS_LOG(LOG_VERB, "Removing Echo Test session...\n");
410
        janus_mutex_lock(&sessions_mutex);
411
        if(!session->destroyed) {
412
                session->destroyed = janus_get_monotonic_time();
413
                g_hash_table_remove(sessions, handle);
414
                /* Cleaning up and removing the session is done in a lazy way */
415
                old_sessions = g_list_append(old_sessions, session);
416
        }
417
        janus_mutex_unlock(&sessions_mutex);
418
        return;
419
}
420

    
421
char *janus_echotest_query_session(janus_plugin_session *handle) {
422
        if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized)) {
423
                return NULL;
424
        }        
425
        janus_echotest_session *session = (janus_echotest_session *)handle->plugin_handle;
426
        if(!session) {
427
                JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
428
                return NULL;
429
        }
430
        /* In the echo test, every session is the same: we just provide some configure info */
431
        json_t *info = json_object();
432
        json_object_set_new(info, "audio_active", json_string(session->audio_active ? "true" : "false"));
433
        json_object_set_new(info, "video_active", json_string(session->video_active ? "true" : "false"));
434
        json_object_set_new(info, "bitrate", json_integer(session->bitrate));
435
        if(session->arc || session->vrc || session->drc) {
436
                json_t *recording = json_object();
437
                if(session->arc && session->arc->filename)
438
                        json_object_set_new(recording, "audio", json_string(session->arc->filename));
439
                if(session->vrc && session->vrc->filename)
440
                        json_object_set_new(recording, "video", json_string(session->vrc->filename));
441
                if(session->drc && session->drc->filename)
442
                        json_object_set_new(recording, "data", json_string(session->drc->filename));
443
                json_object_set_new(info, "recording", recording);
444
        }
445
        json_object_set_new(info, "slowlink_count", json_integer(session->slowlink_count));
446
        json_object_set_new(info, "destroyed", json_integer(session->destroyed));
447
        char *info_text = json_dumps(info, JSON_INDENT(3) | JSON_PRESERVE_ORDER);
448
        json_decref(info);
449
        return info_text;
450
}
451

    
452
struct janus_plugin_result *janus_echotest_handle_message(janus_plugin_session *handle, char *transaction, char *message, char *sdp_type, char *sdp) {
453
        if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
454
                return janus_plugin_result_new(JANUS_PLUGIN_ERROR, g_atomic_int_get(&stopping) ? "Shutting down" : "Plugin not initialized");
455
        janus_echotest_message *msg = g_malloc0(sizeof(janus_echotest_message));
456
        if(msg == NULL) {
457
                JANUS_LOG(LOG_FATAL, "Memory error!\n");
458
                return janus_plugin_result_new(JANUS_PLUGIN_ERROR, "Memory error");
459
        }
460
        msg->handle = handle;
461
        msg->transaction = transaction;
462
        msg->message = message;
463
        msg->sdp_type = sdp_type;
464
        msg->sdp = sdp;
465
        g_async_queue_push(messages, msg);
466

    
467
        /* All the requests to this plugin are handled asynchronously */
468
        return janus_plugin_result_new(JANUS_PLUGIN_OK_WAIT, "I'm taking my time!");
469
}
470

    
471
void janus_echotest_setup_media(janus_plugin_session *handle) {
472
        JANUS_LOG(LOG_INFO, "WebRTC media is now available\n");
473
        if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
474
                return;
475
        janus_echotest_session *session = (janus_echotest_session *)handle->plugin_handle;        
476
        if(!session) {
477
                JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
478
                return;
479
        }
480
        if(session->destroyed)
481
                return;
482
        g_atomic_int_set(&session->hangingup, 0);
483
        /* We really don't care, as we only send RTP/RTCP we get in the first place back anyway */
484
}
485

    
486
void janus_echotest_incoming_rtp(janus_plugin_session *handle, int video, char *buf, int len) {
487
        if(handle == NULL || handle->stopped || g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
488
                return;
489
        /* Simple echo test */
490
        if(gateway) {
491
                /* Honour the audio/video active flags */
492
                janus_echotest_session *session = (janus_echotest_session *)handle->plugin_handle;        
493
                if(!session) {
494
                        JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
495
                        return;
496
                }
497
                if(session->destroyed)
498
                        return;
499
                if((!video && session->audio_active) || (video && session->video_active)) {
500
                        /* Save the frame if we're recording */
501
                        janus_recorder_save_frame(video ? session->vrc : session->arc, buf, len);
502
                        /* Send the frame back */
503
                        gateway->relay_rtp(handle, video, buf, len);
504
                }
505
        }
506
}
507

    
508
void janus_echotest_incoming_rtcp(janus_plugin_session *handle, int video, char *buf, int len) {
509
        if(handle == NULL || handle->stopped || g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
510
                return;
511
        /* Simple echo test */
512
        if(gateway) {
513
                janus_echotest_session *session = (janus_echotest_session *)handle->plugin_handle;        
514
                if(!session) {
515
                        JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
516
                        return;
517
                }
518
                if(session->destroyed)
519
                        return;
520
                if(session->bitrate > 0)
521
                        janus_rtcp_cap_remb(buf, len, session->bitrate);
522
                gateway->relay_rtcp(handle, video, buf, len);
523
        }
524
}
525

    
526
void janus_echotest_incoming_data(janus_plugin_session *handle, char *buf, int len) {
527
        if(handle == NULL || handle->stopped || g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
528
                return;
529
        /* Simple echo test */
530
        if(gateway) {
531
                janus_echotest_session *session = (janus_echotest_session *)handle->plugin_handle;        
532
                if(!session) {
533
                        JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
534
                        return;
535
                }
536
                if(session->destroyed)
537
                        return;
538
                if(buf == NULL || len <= 0)
539
                        return;
540
                char *text = g_malloc0(len+1);
541
                memcpy(text, buf, len);
542
                *(text+len) = '\0';
543
                JANUS_LOG(LOG_VERB, "Got a DataChannel message (%zu bytes) to bounce back: %s\n", strlen(text), text);
544
                /* Save the frame if we're recording */
545
                janus_recorder_save_frame(session->drc, text, strlen(text));
546
                /* We send back the same text with a custom prefix */
547
                const char *prefix = "Janus EchoTest here! You wrote: ";
548
                char *reply = g_malloc0(strlen(prefix)+len+1);
549
                g_snprintf(reply, strlen(prefix)+len+1, "%s%s", prefix, text);
550
                g_free(text);
551
                gateway->relay_data(handle, reply, strlen(reply));
552
                g_free(reply);
553
        }
554
}
555

    
556
void janus_echotest_slow_link(janus_plugin_session *handle, int uplink, int video) {
557
        /* The core is informing us that our peer got or sent too many NACKs, are we pushing media too hard? */
558
        if(handle == NULL || handle->stopped || g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
559
                return;
560
        janus_echotest_session *session = (janus_echotest_session *)handle->plugin_handle;        
561
        if(!session) {
562
                JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
563
                return;
564
        }
565
        if(session->destroyed)
566
                return;
567
        session->slowlink_count++;
568
        if(uplink && !video && !session->audio_active) {
569
                /* We're not relaying audio and the peer is expecting it, so NACKs are normal */
570
                JANUS_LOG(LOG_VERB, "Getting a lot of NACKs (slow uplink) for audio, but that's expected, a configure disabled the audio forwarding\n");
571
        } else if(uplink && video && !session->video_active) {
572
                /* We're not relaying video and the peer is expecting it, so NACKs are normal */
573
                JANUS_LOG(LOG_VERB, "Getting a lot of NACKs (slow uplink) for video, but that's expected, a configure disabled the video forwarding\n");
574
        } else {
575
                /* Slow uplink or downlink, maybe we set the bitrate cap too high? */
576
                if(video) {
577
                        /* Halve the bitrate, but don't go too low... */
578
                        session->bitrate = session->bitrate > 0 ? session->bitrate : 512*1024;
579
                        session->bitrate = session->bitrate/2;
580
                        if(session->bitrate < 64*1024)
581
                                session->bitrate = 64*1024;
582
                        JANUS_LOG(LOG_WARN, "Getting a lot of NACKs (slow %s) for %s, forcing a lower REMB: %"SCNu64"\n",
583
                                uplink ? "uplink" : "downlink", video ? "video" : "audio", session->bitrate);
584
                        /* ... and send a new REMB back */
585
                        char rtcpbuf[24];
586
                        janus_rtcp_remb((char *)(&rtcpbuf), 24, session->bitrate);
587
                        gateway->relay_rtcp(handle, 1, rtcpbuf, 24);
588
                        /* As a last thing, notify the user about this */
589
                        json_t *event = json_object();
590
                        json_object_set_new(event, "echotest", json_string("event"));
591
                        json_t *result = json_object();
592
                        json_object_set_new(result, "status", json_string("slow_link"));
593
                        json_object_set_new(result, "bitrate", json_integer(session->bitrate));
594
                        json_object_set_new(event, "result", result);
595
                        char *event_text = json_dumps(event, JSON_INDENT(3) | JSON_PRESERVE_ORDER);
596
                        json_decref(event);
597
                        event = NULL;
598
                        gateway->push_event(session->handle, &janus_echotest_plugin, NULL, event_text, NULL, NULL);
599
                        g_free(event_text);
600
                }
601
        }
602
}
603

    
604
void janus_echotest_hangup_media(janus_plugin_session *handle) {
605
        JANUS_LOG(LOG_INFO, "No WebRTC media anymore\n");
606
        if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
607
                return;
608
        janus_echotest_session *session = (janus_echotest_session *)handle->plugin_handle;
609
        if(!session) {
610
                JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
611
                return;
612
        }
613
        if(session->destroyed)
614
                return;
615
        if(g_atomic_int_add(&session->hangingup, 1))
616
                return;
617
        /* Send an event to the browser and tell it's over */
618
        json_t *event = json_object();
619
        json_object_set_new(event, "echotest", json_string("event"));
620
        json_object_set_new(event, "result", json_string("done"));
621
        char *event_text = json_dumps(event, JSON_INDENT(3) | JSON_PRESERVE_ORDER);
622
        json_decref(event);
623
        JANUS_LOG(LOG_VERB, "Pushing event: %s\n", event_text);
624
        int ret = gateway->push_event(handle, &janus_echotest_plugin, NULL, event_text, NULL, NULL);
625
        JANUS_LOG(LOG_VERB, "  >> %d (%s)\n", ret, janus_get_api_error(ret));
626
        g_free(event_text);
627
        /* Get rid of the recorders, if available */
628
        janus_mutex_lock(&session->rec_mutex);
629
        if(session->arc) {
630
                janus_recorder_close(session->arc);
631
                JANUS_LOG(LOG_INFO, "Closed audio recording %s\n", session->arc->filename ? session->arc->filename : "??");
632
                janus_recorder_free(session->arc);
633
        }
634
        session->arc = NULL;
635
        if(session->vrc) {
636
                janus_recorder_close(session->vrc);
637
                JANUS_LOG(LOG_INFO, "Closed video recording %s\n", session->vrc->filename ? session->vrc->filename : "??");
638
                janus_recorder_free(session->vrc);
639
        }
640
        session->vrc = NULL;
641
        if(session->drc) {
642
                janus_recorder_close(session->drc);
643
                JANUS_LOG(LOG_INFO, "Closed data recording %s\n", session->drc->filename ? session->drc->filename : "??");
644
                janus_recorder_free(session->drc);
645
        }
646
        session->drc = NULL;
647
        janus_mutex_unlock(&session->rec_mutex);
648
        /* Reset controls */
649
        session->has_audio = FALSE;
650
        session->has_video = FALSE;
651
        session->has_data = FALSE;
652
        session->audio_active = TRUE;
653
        session->video_active = TRUE;
654
        session->bitrate = 0;
655
}
656

    
657
/* Thread to handle incoming messages */
658
static void *janus_echotest_handler(void *data) {
659
        JANUS_LOG(LOG_VERB, "Joining EchoTest handler thread\n");
660
        janus_echotest_message *msg = NULL;
661
        int error_code = 0;
662
        char *error_cause = g_malloc0(512);
663
        if(error_cause == NULL) {
664
                JANUS_LOG(LOG_FATAL, "Memory error!\n");
665
                return NULL;
666
        }
667
        json_t *root = NULL;
668
        while(g_atomic_int_get(&initialized) && !g_atomic_int_get(&stopping)) {
669
                msg = g_async_queue_pop(messages);
670
                if(msg == NULL)
671
                        continue;
672
                if(msg == &exit_message)
673
                        break;
674
                if(msg->handle == NULL) {
675
                        janus_echotest_message_free(msg);
676
                        continue;
677
                }
678
                janus_echotest_session *session = NULL;
679
                janus_mutex_lock(&sessions_mutex);
680
                if(g_hash_table_lookup(sessions, msg->handle) != NULL ) {
681
                        session = (janus_echotest_session *)msg->handle->plugin_handle;
682
                }
683
                janus_mutex_unlock(&sessions_mutex);
684
                if(!session) {
685
                        JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
686
                        janus_echotest_message_free(msg);
687
                        continue;
688
                }
689
                if(session->destroyed) {
690
                        janus_echotest_message_free(msg);
691
                        continue;
692
                }
693
                /* Handle request */
694
                error_code = 0;
695
                root = NULL;
696
                JANUS_LOG(LOG_VERB, "Handling message: %s\n", msg->message);
697
                if(msg->message == NULL) {
698
                        JANUS_LOG(LOG_ERR, "No message??\n");
699
                        error_code = JANUS_ECHOTEST_ERROR_NO_MESSAGE;
700
                        g_snprintf(error_cause, 512, "%s", "No message??");
701
                        goto error;
702
                }
703
                json_error_t error;
704
                root = json_loads(msg->message, 0, &error);
705
                if(!root) {
706
                        JANUS_LOG(LOG_ERR, "JSON error: on line %d: %s\n", error.line, error.text);
707
                        error_code = JANUS_ECHOTEST_ERROR_INVALID_JSON;
708
                        g_snprintf(error_cause, 512, "JSON error: on line %d: %s", error.line, error.text);
709
                        goto error;
710
                }
711
                if(!json_is_object(root)) {
712
                        JANUS_LOG(LOG_ERR, "JSON error: not an object\n");
713
                        error_code = JANUS_ECHOTEST_ERROR_INVALID_JSON;
714
                        g_snprintf(error_cause, 512, "JSON error: not an object");
715
                        goto error;
716
                }
717
                /* Parse request */
718
                json_t *audio = json_object_get(root, "audio");
719
                if(audio && !json_is_boolean(audio)) {
720
                        JANUS_LOG(LOG_ERR, "Invalid element (audio should be a boolean)\n");
721
                        error_code = JANUS_ECHOTEST_ERROR_INVALID_ELEMENT;
722
                        g_snprintf(error_cause, 512, "Invalid value (audio should be a boolean)");
723
                        goto error;
724
                }
725
                json_t *video = json_object_get(root, "video");
726
                if(video && !json_is_boolean(video)) {
727
                        JANUS_LOG(LOG_ERR, "Invalid element (video should be a boolean)\n");
728
                        error_code = JANUS_ECHOTEST_ERROR_INVALID_ELEMENT;
729
                        g_snprintf(error_cause, 512, "Invalid value (video should be a boolean)");
730
                        goto error;
731
                }
732
                json_t *bitrate = json_object_get(root, "bitrate");
733
                if(bitrate && (!json_is_integer(bitrate) || json_integer_value(bitrate) < 0)) {
734
                        JANUS_LOG(LOG_ERR, "Invalid element (bitrate should be a positive integer)\n");
735
                        error_code = JANUS_ECHOTEST_ERROR_INVALID_ELEMENT;
736
                        g_snprintf(error_cause, 512, "Invalid value (bitrate should be a positive integer)");
737
                        goto error;
738
                }
739
                json_t *record = json_object_get(root, "record");
740
                if(record && !json_is_boolean(record)) {
741
                        JANUS_LOG(LOG_ERR, "Invalid element (record should be a boolean)\n");
742
                        error_code = JANUS_ECHOTEST_ERROR_INVALID_ELEMENT;
743
                        g_snprintf(error_cause, 512, "Invalid value (record should be a boolean)");
744
                        goto error;
745
                }
746
                json_t *recfile = json_object_get(root, "filename");
747
                if(recfile && !json_is_string(recfile)) {
748
                        JANUS_LOG(LOG_ERR, "Invalid element (filename should be a string)\n");
749
                        error_code = JANUS_ECHOTEST_ERROR_INVALID_ELEMENT;
750
                        g_snprintf(error_cause, 512, "Invalid value (filename should be a string)");
751
                        goto error;
752
                }
753
                /* Enforce request */
754
                if(audio) {
755
                        session->audio_active = json_is_true(audio);
756
                        JANUS_LOG(LOG_VERB, "Setting audio property: %s\n", session->audio_active ? "true" : "false");
757
                }
758
                if(video) {
759
                        if(!session->video_active && json_is_true(video)) {
760
                                /* Send a PLI */
761
                                JANUS_LOG(LOG_VERB, "Just (re-)enabled video, sending a PLI to recover it\n");
762
                                char buf[12];
763
                                memset(buf, 0, 12);
764
                                janus_rtcp_pli((char *)&buf, 12);
765
                                gateway->relay_rtcp(session->handle, 1, buf, 12);
766
                        }
767
                        session->video_active = json_is_true(video);
768
                        JANUS_LOG(LOG_VERB, "Setting video property: %s\n", session->video_active ? "true" : "false");
769
                }
770
                if(bitrate) {
771
                        session->bitrate = json_integer_value(bitrate);
772
                        JANUS_LOG(LOG_VERB, "Setting video bitrate: %"SCNu64"\n", session->bitrate);
773
                        if(session->bitrate > 0) {
774
                                /* FIXME Generate a new REMB (especially useful for Firefox, which doesn't send any we can cap later) */
775
                                char buf[24];
776
                                memset(buf, 0, 24);
777
                                janus_rtcp_remb((char *)&buf, 24, session->bitrate);
778
                                JANUS_LOG(LOG_VERB, "Sending REMB\n");
779
                                gateway->relay_rtcp(session->handle, 1, buf, 24);
780
                                /* FIXME How should we handle a subsequent "no limit" bitrate? */
781
                        }
782
                }
783
                if(record) {
784
                        if(msg->sdp) {
785
                                session->has_audio = (strstr(msg->sdp, "m=audio") != NULL);
786
                                session->has_video = (strstr(msg->sdp, "m=video") != NULL);
787
                                session->has_data = (strstr(msg->sdp, "DTLS/SCTP") != NULL);
788
                        }
789
                        gboolean recording = json_is_true(record);
790
                        const char *recording_base = json_string_value(recfile);
791
                        JANUS_LOG(LOG_VERB, "Recording %s (base filename: %s)\n", recording ? "enabled" : "disabled", recording_base ? recording_base : "not provided");
792
                        janus_mutex_lock(&session->rec_mutex);
793
                        if(!recording) {
794
                                /* Not recording (anymore?) */
795
                                if(session->arc) {
796
                                        janus_recorder_close(session->arc);
797
                                        JANUS_LOG(LOG_INFO, "Closed audio recording %s\n", session->arc->filename ? session->arc->filename : "??");
798
                                        janus_recorder_free(session->arc);
799
                                }
800
                                session->arc = NULL;
801
                                if(session->vrc) {
802
                                        janus_recorder_close(session->vrc);
803
                                        JANUS_LOG(LOG_INFO, "Closed video recording %s\n", session->vrc->filename ? session->vrc->filename : "??");
804
                                        janus_recorder_free(session->vrc);
805
                                }
806
                                session->vrc = NULL;
807
                                if(session->drc) {
808
                                        janus_recorder_close(session->drc);
809
                                        JANUS_LOG(LOG_INFO, "Closed data recording %s\n", session->drc->filename ? session->drc->filename : "??");
810
                                        janus_recorder_free(session->drc);
811
                                }
812
                                session->drc = NULL;
813
                        } else {
814
                                /* We've started recording, send a PLI and go on */
815
                                char filename[255];
816
                                gint64 now = janus_get_real_time();
817
                                if(session->has_audio) {
818
                                        /* FIXME We assume we're recording Opus, here */
819
                                        memset(filename, 0, 255);
820
                                        if(recording_base) {
821
                                                /* Use the filename and path we have been provided */
822
                                                g_snprintf(filename, 255, "%s-audio", recording_base);
823
                                                session->arc = janus_recorder_create(NULL, "opus", filename);
824
                                                if(session->arc == NULL) {
825
                                                        /* FIXME We should notify the fact the recorder could not be created */
826
                                                        JANUS_LOG(LOG_ERR, "Couldn't open an audio recording file for this EchoTest user!\n");
827
                                                }
828
                                        } else {
829
                                                /* Build a filename */
830
                                                g_snprintf(filename, 255, "echotest-%p-%"SCNi64"-audio", session, now);
831
                                                session->arc = janus_recorder_create(NULL, "opus", filename);
832
                                                if(session->arc == NULL) {
833
                                                        /* FIXME We should notify the fact the recorder could not be created */
834
                                                        JANUS_LOG(LOG_ERR, "Couldn't open an audio recording file for this EchoTest user!\n");
835
                                                }
836
                                        }
837
                                }
838
                                if(session->has_video) {
839
                                        /* FIXME We assume we're recording VP8, here */
840
                                        memset(filename, 0, 255);
841
                                        if(recording_base) {
842
                                                /* Use the filename and path we have been provided */
843
                                                g_snprintf(filename, 255, "%s-video", recording_base);
844
                                                session->vrc = janus_recorder_create(NULL, "vp8", filename);
845
                                                if(session->vrc == NULL) {
846
                                                        /* FIXME We should notify the fact the recorder could not be created */
847
                                                        JANUS_LOG(LOG_ERR, "Couldn't open an video recording file for this EchoTest user!\n");
848
                                                }
849
                                        } else {
850
                                                /* Build a filename */
851
                                                g_snprintf(filename, 255, "echotest-%p-%"SCNi64"-video", session, now);
852
                                                session->vrc = janus_recorder_create(NULL, "vp8", filename);
853
                                                if(session->vrc == NULL) {
854
                                                        /* FIXME We should notify the fact the recorder could not be created */
855
                                                        JANUS_LOG(LOG_ERR, "Couldn't open an video recording file for this EchoTest user!\n");
856
                                                }
857
                                        }
858
                                        /* Send a PLI */
859
                                        JANUS_LOG(LOG_VERB, "Recording video, sending a PLI to kickstart it\n");
860
                                        char buf[12];
861
                                        memset(buf, 0, 12);
862
                                        janus_rtcp_pli((char *)&buf, 12);
863
                                        gateway->relay_rtcp(session->handle, 1, buf, 12);
864
                                }
865
                                if(session->has_data) {
866
                                        memset(filename, 0, 255);
867
                                        if(recording_base) {
868
                                                /* Use the filename and path we have been provided */
869
                                                g_snprintf(filename, 255, "%s-data", recording_base);
870
                                                session->drc = janus_recorder_create(NULL, "text", filename);
871
                                                if(session->drc == NULL) {
872
                                                        /* FIXME We should notify the fact the recorder could not be created */
873
                                                        JANUS_LOG(LOG_ERR, "Couldn't open a text data recording file for this EchoTest user!\n");
874
                                                }
875
                                        } else {
876
                                                /* Build a filename */
877
                                                g_snprintf(filename, 255, "echotest-%p-%"SCNi64"-data", session, now);
878
                                                session->drc = janus_recorder_create(NULL, "text", filename);
879
                                                if(session->drc == NULL) {
880
                                                        /* FIXME We should notify the fact the recorder could not be created */
881
                                                        JANUS_LOG(LOG_ERR, "Couldn't open a text data recording file for this EchoTest user!\n");
882
                                                }
883
                                        }
884
                                }
885
                        }
886
                        janus_mutex_unlock(&session->rec_mutex);
887
                }
888
                /* Any SDP to handle? */
889
                if(msg->sdp) {
890
                        JANUS_LOG(LOG_VERB, "This is involving a negotiation (%s) as well:\n%s\n", msg->sdp_type, msg->sdp);
891
                        session->has_audio = (strstr(msg->sdp, "m=audio") != NULL);
892
                        session->has_video = (strstr(msg->sdp, "m=video") != NULL);
893
                        session->has_data = (strstr(msg->sdp, "DTLS/SCTP") != NULL);
894
                }
895

    
896
                if(!audio && !video && !bitrate && !record && !msg->sdp) {
897
                        JANUS_LOG(LOG_ERR, "No supported attributes (audio, video, bitrate, record, jsep) found\n");
898
                        error_code = JANUS_ECHOTEST_ERROR_INVALID_ELEMENT;
899
                        g_snprintf(error_cause, 512, "Message error: no supported attributes (audio, video, bitrate, record, jsep) found");
900
                        goto error;
901
                }
902

    
903
                json_decref(root);
904
                /* Prepare JSON event */
905
                json_t *event = json_object();
906
                json_object_set_new(event, "echotest", json_string("event"));
907
                json_object_set_new(event, "result", json_string("ok"));
908
                char *event_text = json_dumps(event, JSON_INDENT(3) | JSON_PRESERVE_ORDER);
909
                json_decref(event);
910
                JANUS_LOG(LOG_VERB, "Pushing event: %s\n", event_text);
911
                if(!msg->sdp) {
912
                        int ret = gateway->push_event(msg->handle, &janus_echotest_plugin, msg->transaction, event_text, NULL, NULL);
913
                        JANUS_LOG(LOG_VERB, "  >> %d (%s)\n", ret, janus_get_api_error(ret));
914
                } else {
915
                        /* Forward the same offer to the gateway, to start the echo test */
916
                        const char *type = NULL;
917
                        if(!strcasecmp(msg->sdp_type, "offer"))
918
                                type = "answer";
919
                        if(!strcasecmp(msg->sdp_type, "answer"))
920
                                type = "offer";
921
                        /* Any media direction that needs to be fixed? */
922
                        char *sdp = g_strdup(msg->sdp);
923
                        if(strstr(sdp, "a=recvonly")) {
924
                                /* Turn recvonly to inactive, as we simply bounce media back */
925
                                sdp = janus_string_replace(sdp, "a=recvonly", "a=inactive");
926
                        } else if(strstr(sdp, "a=sendonly")) {
927
                                /* Turn sendonly to recvonly */
928
                                sdp = janus_string_replace(sdp, "a=sendonly", "a=recvonly");
929
                                /* FIXME We should also actually not echo this media back, though... */
930
                        }
931
                        /* Make also sure we get rid of ULPfec, red, etc. */
932
                        if(strstr(sdp, "ulpfec")) {
933
                                /* FIXME This really needs some better code */
934
                                sdp = janus_string_replace(sdp, "a=rtpmap:116 red/90000\r\n", "");
935
                                sdp = janus_string_replace(sdp, "a=rtpmap:117 ulpfec/90000\r\n", "");
936
                                sdp = janus_string_replace(sdp, "a=rtpmap:96 rtx/90000\r\n", "");
937
                                sdp = janus_string_replace(sdp, "a=fmtp:96 apt=100\r\n", "");
938
                                sdp = janus_string_replace(sdp, "a=rtpmap:97 rtx/90000\r\n", "");
939
                                sdp = janus_string_replace(sdp, "a=fmtp:97 apt=101\r\n", "");
940
                                sdp = janus_string_replace(sdp, "a=rtpmap:98 rtx/90000\r\n", "");
941
                                sdp = janus_string_replace(sdp, "a=fmtp:98 apt=116\r\n", "");
942
                                sdp = janus_string_replace(sdp, " 116", "");
943
                                sdp = janus_string_replace(sdp, " 117", "");
944
                                sdp = janus_string_replace(sdp, " 96", "");
945
                                sdp = janus_string_replace(sdp, " 97", "");
946
                                sdp = janus_string_replace(sdp, " 98", "");
947
                        }
948
                        /* How long will the gateway take to push the event? */
949
                        g_atomic_int_set(&session->hangingup, 0);
950
                        gint64 start = janus_get_monotonic_time();
951
                        int res = gateway->push_event(msg->handle, &janus_echotest_plugin, msg->transaction, event_text, type, sdp);
952
                        JANUS_LOG(LOG_VERB, "  >> Pushing event: %d (took %"SCNu64" us)\n",
953
                                res, janus_get_monotonic_time()-start);
954
                        g_free(sdp);
955
                }
956
                g_free(event_text);
957
                janus_echotest_message_free(msg);
958
                continue;
959
                
960
error:
961
                {
962
                        if(root != NULL)
963
                                json_decref(root);
964
                        /* Prepare JSON error event */
965
                        json_t *event = json_object();
966
                        json_object_set_new(event, "echotest", json_string("event"));
967
                        json_object_set_new(event, "error_code", json_integer(error_code));
968
                        json_object_set_new(event, "error", json_string(error_cause));
969
                        char *event_text = json_dumps(event, JSON_INDENT(3) | JSON_PRESERVE_ORDER);
970
                        json_decref(event);
971
                        JANUS_LOG(LOG_VERB, "Pushing event: %s\n", event_text);
972
                        int ret = gateway->push_event(msg->handle, &janus_echotest_plugin, msg->transaction, event_text, NULL, NULL);
973
                        JANUS_LOG(LOG_VERB, "  >> %d (%s)\n", ret, janus_get_api_error(ret));
974
                        g_free(event_text);
975
                        janus_echotest_message_free(msg);
976
                }
977
        }
978
        g_free(error_cause);
979
        JANUS_LOG(LOG_VERB, "Leaving EchoTest handler thread\n");
980
        return NULL;
981
}