Statistics
| Branch: | Revision:

janus-gateway / plugins / janus_echotest.c @ a1ea0562

History | View | Annotate | Download (34.6 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 *watchdog;
166

    
167
static janus_mutex handler_mutex;
168
static janus_condition handler_cond;
169
static GThread *handler_thread;
170
static void *janus_echotest_handler(void *data);
171

    
172

    
173
typedef struct janus_echotest_message {
174
        janus_plugin_session *handle;
175
        char *transaction;
176
        char *message;
177
        char *sdp_type;
178
        char *sdp;
179
} janus_echotest_message;
180
static GQueue *messages = NULL;
181

    
182
typedef struct janus_echotest_session {
183
        janus_plugin_session *handle;
184
        gboolean has_audio;
185
        gboolean has_video;
186
        gboolean audio_active;
187
        gboolean video_active;
188
        uint64_t bitrate;
189
        janus_recorder *arc;        /* The Janus recorder instance for this user's audio, if enabled */
190
        janus_recorder *vrc;        /* The Janus recorder instance for this user's video, if enabled */
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
void janus_echotest_message_free(janus_echotest_message *msg);
200
void janus_echotest_message_free(janus_echotest_message *msg) {
201
        if(!msg)
202
                return;
203

    
204
        msg->handle = NULL;
205

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

    
215
        g_free(msg);
216
}
217

    
218

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

    
224

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

    
264

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

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

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

    
315
void janus_echotest_destroy(void) {
316
        if(!g_atomic_int_get(&initialized))
317
                return;
318
        g_atomic_int_set(&stopping, 1);
319

    
320
        janus_mutex_lock(&handler_mutex);
321
        janus_condition_signal(&handler_cond);
322
        janus_mutex_unlock(&handler_mutex);
323
        if(handler_thread != NULL) {
324
                g_thread_join(handler_thread);
325
                handler_thread = NULL;
326
        }
327
        if(watchdog != NULL) {
328
                g_thread_join(watchdog);
329
                watchdog = NULL;
330
        }
331

    
332
        /* FIXME We should destroy the sessions cleanly */
333
        janus_mutex_lock(&sessions_mutex);
334
        g_hash_table_destroy(sessions);
335
        janus_mutex_unlock(&sessions_mutex);
336
        g_queue_free_full(messages, (GDestroyNotify) janus_echotest_message_free);
337
        messages = NULL;
338
        sessions = NULL;
339

    
340
        g_atomic_int_set(&initialized, 0);
341
        g_atomic_int_set(&stopping, 0);
342
        JANUS_LOG(LOG_INFO, "%s destroyed!\n", JANUS_ECHOTEST_NAME);
343
}
344

    
345
int janus_echotest_get_api_compatibility(void) {
346
        /* Important! This is what your plugin MUST always return: don't lie here or bad things will happen */
347
        return JANUS_PLUGIN_API_VERSION;
348
}
349

    
350
int janus_echotest_get_version(void) {
351
        return JANUS_ECHOTEST_VERSION;
352
}
353

    
354
const char *janus_echotest_get_version_string(void) {
355
        return JANUS_ECHOTEST_VERSION_STRING;
356
}
357

    
358
const char *janus_echotest_get_description(void) {
359
        return JANUS_ECHOTEST_DESCRIPTION;
360
}
361

    
362
const char *janus_echotest_get_name(void) {
363
        return JANUS_ECHOTEST_NAME;
364
}
365

    
366
const char *janus_echotest_get_author(void) {
367
        return JANUS_ECHOTEST_AUTHOR;
368
}
369

    
370
const char *janus_echotest_get_package(void) {
371
        return JANUS_ECHOTEST_PACKAGE;
372
}
373

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

    
398
        return;
399
}
400

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

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

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

    
471
        /* All the requests to this plugin are handled asynchronously */
472
        return janus_plugin_result_new(JANUS_PLUGIN_OK_WAIT, "I'm taking my time!");
473
}
474

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

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

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

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

    
561
void janus_echotest_slow_link(janus_plugin_session *handle, int uplink, int video) {
562
        /* The core is informing us that our peer got or sent too many NACKs, are we pushing media too hard? */
563
        if(handle == NULL || handle->stopped || g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
564
                return;
565
        janus_echotest_session *session = (janus_echotest_session *)handle->plugin_handle;        
566
        if(!session) {
567
                JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
568
                return;
569
        }
570
        if(session->destroyed)
571
                return;
572
        session->slowlink_count++;
573
        if(uplink && !video && !session->audio_active) {
574
                /* We're not relaying audio and the peer is expecting it, so NACKs are normal */
575
                JANUS_LOG(LOG_VERB, "Getting a lot of NACKs (slow uplink) for audio, but that's expected, a configure disabled the audio forwarding\n");
576
        } else if(uplink && video && !session->video_active) {
577
                /* We're not relaying video and the peer is expecting it, so NACKs are normal */
578
                JANUS_LOG(LOG_VERB, "Getting a lot of NACKs (slow uplink) for video, but that's expected, a configure disabled the video forwarding\n");
579
        } else {
580
                /* Slow uplink or downlink, maybe we set the bitrate cap too high? */
581
                if(video) {
582
                        /* Halve the bitrate, but don't go too low... */
583
                        session->bitrate = session->bitrate > 0 ? session->bitrate : 512*1024;
584
                        session->bitrate = session->bitrate/2;
585
                        if(session->bitrate < 64*1024)
586
                                session->bitrate = 64*1024;
587
                        JANUS_LOG(LOG_WARN, "Getting a lot of NACKs (slow %s) for %s, forcing a lower REMB: %"SCNu64"\n",
588
                                uplink ? "uplink" : "downlink", video ? "video" : "audio", session->bitrate);
589
                        /* ... and send a new REMB back */
590
                        char rtcpbuf[200];
591
                        memset(rtcpbuf, 0, 200);
592
                        /* FIXME First put a RR (fake)... */
593
                        int rrlen = 32;
594
                        rtcp_rr *rr = (rtcp_rr *)&rtcpbuf;
595
                        rr->header.version = 2;
596
                        rr->header.type = RTCP_RR;
597
                        rr->header.rc = 1;
598
                        rr->header.length = htons((rrlen/4)-1);
599
                        /* ... then put a SDES... */
600
                        int sdeslen = janus_rtcp_sdes((char *)(&rtcpbuf)+rrlen, 200-rrlen, "janusvideo", 10);
601
                        if(sdeslen > 0) {
602
                                /* ... and then finally a REMB */
603
                                janus_rtcp_remb((char *)(&rtcpbuf)+rrlen+sdeslen, 24, session->bitrate);
604
                                gateway->relay_rtcp(handle, 1, rtcpbuf, rrlen+sdeslen+24);
605
                        }
606
                        /* As a last thing, notify the user about this */
607
                        json_t *event = json_object();
608
                        json_object_set_new(event, "echotest", json_string("event"));
609
                        json_t *result = json_object();
610
                        json_object_set_new(result, "status", json_string("slow_link"));
611
                        json_object_set_new(result, "bitrate", json_integer(session->bitrate));
612
                        json_object_set_new(event, "result", result);
613
                        char *event_text = json_dumps(event, JSON_INDENT(3) | JSON_PRESERVE_ORDER);
614
                        json_decref(event);
615
                        json_decref(result);
616
                        event = NULL;
617
                        gateway->push_event(session->handle, &janus_echotest_plugin, NULL, event_text, NULL, NULL);
618
                        g_free(event_text);
619
                }
620
        }
621
}
622

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

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

    
876
                if(!audio && !video && !bitrate && !record && !msg->sdp) {
877
                        JANUS_LOG(LOG_ERR, "No supported attributes (audio, video, bitrate, record, jsep) found\n");
878
                        error_code = JANUS_ECHOTEST_ERROR_INVALID_ELEMENT;
879
                        g_snprintf(error_cause, 512, "Message error: no supported attributes (audio, video, bitrate, record, jsep) found");
880
                        goto error;
881
                }
882

    
883
                json_decref(root);
884
                /* Prepare JSON event */
885
                json_t *event = json_object();
886
                json_object_set_new(event, "echotest", json_string("event"));
887
                json_object_set_new(event, "result", json_string("ok"));
888
                char *event_text = json_dumps(event, JSON_INDENT(3) | JSON_PRESERVE_ORDER);
889
                json_decref(event);
890
                JANUS_LOG(LOG_VERB, "Pushing event: %s\n", event_text);
891
                if(!msg->sdp) {
892
                        int ret = gateway->push_event(msg->handle, &janus_echotest_plugin, msg->transaction, event_text, NULL, NULL);
893
                        JANUS_LOG(LOG_VERB, "  >> %d (%s)\n", ret, janus_get_api_error(ret));
894
                } else {
895
                        /* Forward the same offer to the gateway, to start the echo test */
896
                        const char *type = NULL;
897
                        if(!strcasecmp(msg->sdp_type, "offer"))
898
                                type = "answer";
899
                        if(!strcasecmp(msg->sdp_type, "answer"))
900
                                type = "offer";
901
                        /* Any media direction that needs to be fixed? */
902
                        char *sdp = g_strdup(msg->sdp);
903
                        if(strstr(sdp, "a=recvonly")) {
904
                                /* Turn recvonly to inactive, as we simply bounce media back */
905
                                sdp = janus_string_replace(sdp, "a=recvonly", "a=inactive");
906
                        } else if(strstr(sdp, "a=sendonly")) {
907
                                /* Turn sendonly to recvonly */
908
                                sdp = janus_string_replace(sdp, "a=sendonly", "a=recvonly");
909
                                /* FIXME We should also actually not echo this media back, though... */
910
                        }
911
                        /* Make also sure we get rid of ULPfec, red, etc. */
912
                        if(strstr(sdp, "ulpfec")) {
913
                                sdp = janus_string_replace(sdp, "100 116 117 96", "100");
914
                                sdp = janus_string_replace(sdp, "a=rtpmap:116 red/90000\r\n", "");
915
                                sdp = janus_string_replace(sdp, "a=rtpmap:117 ulpfec/90000\r\n", "");
916
                                sdp = janus_string_replace(sdp, "a=rtpmap:96 rtx/90000\r\n", "");
917
                                sdp = janus_string_replace(sdp, "a=fmtp:96 apt=100\r\n", "");
918
                        }
919
                        /* How long will the gateway take to push the event? */
920
                        gint64 start = janus_get_monotonic_time();
921
                        int res = gateway->push_event(msg->handle, &janus_echotest_plugin, msg->transaction, event_text, type, sdp);
922
                        JANUS_LOG(LOG_VERB, "  >> Pushing event: %d (took %"SCNu64" us)\n",
923
                                res, janus_get_monotonic_time()-start);
924
                        g_free(sdp);
925
                }
926
                g_free(event_text);
927
                janus_echotest_message_free(msg);
928
                continue;
929
                
930
error:
931
                {
932
                        if(root != NULL)
933
                                json_decref(root);
934
                        /* Prepare JSON error event */
935
                        json_t *event = json_object();
936
                        json_object_set_new(event, "echotest", json_string("event"));
937
                        json_object_set_new(event, "error_code", json_integer(error_code));
938
                        json_object_set_new(event, "error", json_string(error_cause));
939
                        char *event_text = json_dumps(event, JSON_INDENT(3) | JSON_PRESERVE_ORDER);
940
                        json_decref(event);
941
                        JANUS_LOG(LOG_VERB, "Pushing event: %s\n", event_text);
942
                        int ret = gateway->push_event(msg->handle, &janus_echotest_plugin, msg->transaction, event_text, NULL, NULL);
943
                        JANUS_LOG(LOG_VERB, "  >> %d (%s)\n", ret, janus_get_api_error(ret));
944
                        g_free(event_text);
945
                        janus_echotest_message_free(msg);
946
                }
947
        }
948
        g_free(error_cause);
949
        JANUS_LOG(LOG_VERB, "Leaving EchoTest handler thread\n");
950
        return NULL;
951
}