Statistics
| Branch: | Revision:

janus-gateway / plugins / janus_echotest.c @ 8d38fbcc

History | View | Annotate | Download (34.2 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

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

    
195
void janus_echotest_message_free(janus_echotest_message *msg);
196
void janus_echotest_message_free(janus_echotest_message *msg) {
197
        if(!msg)
198
                return;
199

    
200
        msg->handle = NULL;
201

    
202
        g_free(msg->transaction);
203
        msg->transaction = NULL;
204
        g_free(msg->message);
205
        msg->message = NULL;
206
        g_free(msg->sdp_type);
207
        msg->sdp_type = NULL;
208
        g_free(msg->sdp);
209
        msg->sdp = NULL;
210

    
211
        g_free(msg);
212
}
213

    
214

    
215
/* Error codes */
216
#define JANUS_ECHOTEST_ERROR_NO_MESSAGE                        411
217
#define JANUS_ECHOTEST_ERROR_INVALID_JSON                412
218
#define JANUS_ECHOTEST_ERROR_INVALID_ELEMENT        413
219

    
220

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

    
260

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

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

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

    
309
void janus_echotest_destroy(void) {
310
        if(!g_atomic_int_get(&initialized))
311
                return;
312
        g_atomic_int_set(&stopping, 1);
313

    
314
        g_async_queue_push(messages, g_malloc0(sizeof(janus_echotest_message)));
315
        if(handler_thread != NULL) {
316
                g_thread_join(handler_thread);
317
                handler_thread = NULL;
318
        }
319
        if(watchdog != NULL) {
320
                g_thread_join(watchdog);
321
                watchdog = NULL;
322
        }
323

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

    
332
        g_atomic_int_set(&initialized, 0);
333
        g_atomic_int_set(&stopping, 0);
334
        JANUS_LOG(LOG_INFO, "%s destroyed!\n", JANUS_ECHOTEST_NAME);
335
}
336

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

    
342
int janus_echotest_get_version(void) {
343
        return JANUS_ECHOTEST_VERSION;
344
}
345

    
346
const char *janus_echotest_get_version_string(void) {
347
        return JANUS_ECHOTEST_VERSION_STRING;
348
}
349

    
350
const char *janus_echotest_get_description(void) {
351
        return JANUS_ECHOTEST_DESCRIPTION;
352
}
353

    
354
const char *janus_echotest_get_name(void) {
355
        return JANUS_ECHOTEST_NAME;
356
}
357

    
358
const char *janus_echotest_get_author(void) {
359
        return JANUS_ECHOTEST_AUTHOR;
360
}
361

    
362
const char *janus_echotest_get_package(void) {
363
        return JANUS_ECHOTEST_PACKAGE;
364
}
365

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

    
390
        return;
391
}
392

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

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

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

    
460
        /* All the requests to this plugin are handled asynchronously */
461
        return janus_plugin_result_new(JANUS_PLUGIN_OK_WAIT, "I'm taking my time!");
462
}
463

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

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

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

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

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

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

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

    
861
                if(!audio && !video && !bitrate && !record && !msg->sdp) {
862
                        JANUS_LOG(LOG_ERR, "No supported attributes (audio, video, bitrate, record, jsep) found\n");
863
                        error_code = JANUS_ECHOTEST_ERROR_INVALID_ELEMENT;
864
                        g_snprintf(error_cause, 512, "Message error: no supported attributes (audio, video, bitrate, record, jsep) found");
865
                        goto error;
866
                }
867

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