Statistics
| Branch: | Revision:

janus-gateway / plugins / janus_echotest.c @ 1d31e31f

History | View | Annotate | Download (35.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
static janus_echotest_message exit_message;
178

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

    
196
static void janus_echotest_message_free(janus_echotest_message *msg) {
197
        if(!msg || msg == &exit_message)
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, &exit_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[24];
580
                        janus_rtcp_remb((char *)(&rtcpbuf), 24, session->bitrate);
581
                        gateway->relay_rtcp(handle, 1, rtcpbuf, 24);
582
                        /* As a last thing, notify the user about this */
583
                        json_t *event = json_object();
584
                        json_object_set_new(event, "echotest", json_string("event"));
585
                        json_t *result = json_object();
586
                        json_object_set_new(result, "status", json_string("slow_link"));
587
                        json_object_set_new(result, "bitrate", json_integer(session->bitrate));
588
                        json_object_set_new(event, "result", result);
589
                        char *event_text = json_dumps(event, JSON_INDENT(3) | JSON_PRESERVE_ORDER);
590
                        json_decref(event);
591
                        json_decref(result);
592
                        event = NULL;
593
                        gateway->push_event(session->handle, &janus_echotest_plugin, NULL, event_text, NULL, NULL);
594
                        g_free(event_text);
595
                }
596
        }
597
}
598

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

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

    
850
                if(!audio && !video && !bitrate && !record && !msg->sdp) {
851
                        JANUS_LOG(LOG_ERR, "No supported attributes (audio, video, bitrate, record, jsep) found\n");
852
                        error_code = JANUS_ECHOTEST_ERROR_INVALID_ELEMENT;
853
                        g_snprintf(error_cause, 512, "Message error: no supported attributes (audio, video, bitrate, record, jsep) found");
854
                        goto error;
855
                }
856

    
857
                json_decref(root);
858
                /* Prepare JSON event */
859
                json_t *event = json_object();
860
                json_object_set_new(event, "echotest", json_string("event"));
861
                json_object_set_new(event, "result", json_string("ok"));
862
                char *event_text = json_dumps(event, JSON_INDENT(3) | JSON_PRESERVE_ORDER);
863
                json_decref(event);
864
                JANUS_LOG(LOG_VERB, "Pushing event: %s\n", event_text);
865
                if(!msg->sdp) {
866
                        int ret = gateway->push_event(msg->handle, &janus_echotest_plugin, msg->transaction, event_text, NULL, NULL);
867
                        JANUS_LOG(LOG_VERB, "  >> %d (%s)\n", ret, janus_get_api_error(ret));
868
                } else {
869
                        /* Forward the same offer to the gateway, to start the echo test */
870
                        const char *type = NULL;
871
                        if(!strcasecmp(msg->sdp_type, "offer"))
872
                                type = "answer";
873
                        if(!strcasecmp(msg->sdp_type, "answer"))
874
                                type = "offer";
875
                        /* Any media direction that needs to be fixed? */
876
                        char *sdp = g_strdup(msg->sdp);
877
                        if(strstr(sdp, "a=recvonly")) {
878
                                /* Turn recvonly to inactive, as we simply bounce media back */
879
                                sdp = janus_string_replace(sdp, "a=recvonly", "a=inactive");
880
                        } else if(strstr(sdp, "a=sendonly")) {
881
                                /* Turn sendonly to recvonly */
882
                                sdp = janus_string_replace(sdp, "a=sendonly", "a=recvonly");
883
                                /* FIXME We should also actually not echo this media back, though... */
884
                        }
885
                        /* Make also sure we get rid of ULPfec, red, etc. */
886
                        if(strstr(sdp, "ulpfec")) {
887
                                /* FIXME This really needs some better code */
888
                                sdp = janus_string_replace(sdp, "a=rtpmap:116 red/90000\r\n", "");
889
                                sdp = janus_string_replace(sdp, "a=rtpmap:117 ulpfec/90000\r\n", "");
890
                                sdp = janus_string_replace(sdp, "a=rtpmap:96 rtx/90000\r\n", "");
891
                                sdp = janus_string_replace(sdp, "a=fmtp:96 apt=100\r\n", "");
892
                                sdp = janus_string_replace(sdp, "a=rtpmap:97 rtx/90000\r\n", "");
893
                                sdp = janus_string_replace(sdp, "a=fmtp:97 apt=101\r\n", "");
894
                                sdp = janus_string_replace(sdp, "a=rtpmap:98 rtx/90000\r\n", "");
895
                                sdp = janus_string_replace(sdp, "a=fmtp:98 apt=116\r\n", "");
896
                                sdp = janus_string_replace(sdp, " 116", "");
897
                                sdp = janus_string_replace(sdp, " 117", "");
898
                                sdp = janus_string_replace(sdp, " 96", "");
899
                                sdp = janus_string_replace(sdp, " 97", "");
900
                                sdp = janus_string_replace(sdp, " 98", "");
901
                        }
902
                        /* How long will the gateway take to push the event? */
903
                        g_atomic_int_set(&session->hangingup, 0);
904
                        gint64 start = janus_get_monotonic_time();
905
                        int res = gateway->push_event(msg->handle, &janus_echotest_plugin, msg->transaction, event_text, type, sdp);
906
                        JANUS_LOG(LOG_VERB, "  >> Pushing event: %d (took %"SCNu64" us)\n",
907
                                res, janus_get_monotonic_time()-start);
908
                        g_free(sdp);
909
                }
910
                g_free(event_text);
911
                janus_echotest_message_free(msg);
912

    
913
                //~ if(gateway->events_is_enabled()) {
914
                        /* Just to showcase how you can notify handlers, let's update them on our configuration */
915
                        json_t *info = json_object();
916
                        json_object_set_new(info, "audio_active", json_string(session->audio_active ? "true" : "false"));
917
                        json_object_set_new(info, "video_active", json_string(session->video_active ? "true" : "false"));
918
                        json_object_set_new(info, "bitrate", json_integer(session->bitrate));
919
                        if(session->arc || session->vrc) {
920
                                json_t *recording = json_object();
921
                                if(session->arc && session->arc->filename)
922
                                        json_object_set_new(recording, "audio", json_string(session->arc->filename));
923
                                if(session->vrc && session->vrc->filename)
924
                                        json_object_set_new(recording, "video", json_string(session->vrc->filename));
925
                                json_object_set_new(info, "recording", recording);
926
                        }
927
                        gateway->notify_event(session->handle, info);
928
                //~ }
929

    
930
                /* Done, on to the next request */
931
                continue;
932
                
933
error:
934
                {
935
                        if(root != NULL)
936
                                json_decref(root);
937
                        /* Prepare JSON error event */
938
                        json_t *event = json_object();
939
                        json_object_set_new(event, "echotest", json_string("event"));
940
                        json_object_set_new(event, "error_code", json_integer(error_code));
941
                        json_object_set_new(event, "error", json_string(error_cause));
942
                        char *event_text = json_dumps(event, JSON_INDENT(3) | JSON_PRESERVE_ORDER);
943
                        json_decref(event);
944
                        JANUS_LOG(LOG_VERB, "Pushing event: %s\n", event_text);
945
                        int ret = gateway->push_event(msg->handle, &janus_echotest_plugin, msg->transaction, event_text, NULL, NULL);
946
                        JANUS_LOG(LOG_VERB, "  >> %d (%s)\n", ret, janus_get_api_error(ret));
947
                        g_free(event_text);
948
                        janus_echotest_message_free(msg);
949
                }
950
        }
951
        g_free(error_cause);
952
        JANUS_LOG(LOG_VERB, "Leaving EchoTest handler thread\n");
953
        return NULL;
954
}