Statistics
| Branch: | Revision:

janus-gateway / plugins / janus_echotest.c @ 5e9e29e0

History | View | Annotate | Download (14.4 KB)

1
/*! \file   janus_echotest.c
2
 * \author Lorenzo Miniero <lorenzo@meetecho.com>
3
 * \copyright GNU Affero 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
 * \ingroup plugins
22
 * \ref plugins
23
 */
24

    
25
#include "plugin.h"
26

    
27
#include <jansson.h>
28

    
29
#include "../config.h"
30
#include "../rtcp.h"
31
#include "../utils.h"
32

    
33

    
34
/* Plugin information */
35
#define JANUS_ECHOTEST_VERSION                        1
36
#define JANUS_ECHOTEST_VERSION_STRING        "0.0.1"
37
#define JANUS_ECHOTEST_DESCRIPTION                "This is a trivial EchoTest plugin for Janus, just used to showcase the plugin interface."
38
#define JANUS_ECHOTEST_NAME                                "JANUS EchoTest plugin"
39
#define JANUS_ECHOTEST_PACKAGE                        "janus.plugin.echotest"
40

    
41
/* Plugin methods */
42
janus_plugin *create(void);
43
int janus_echotest_init(janus_callbacks *callback, const char *config_path);
44
void janus_echotest_destroy(void);
45
int janus_echotest_get_version(void);
46
const char *janus_echotest_get_version_string(void);
47
const char *janus_echotest_get_description(void);
48
const char *janus_echotest_get_name(void);
49
const char *janus_echotest_get_package(void);
50
void janus_echotest_create_session(janus_pluginession *handle, int *error);
51
void janus_echotest_handle_message(janus_pluginession *handle, char *transaction, char *message, char *sdp_type, char *sdp);
52
void janus_echotest_setup_media(janus_pluginession *handle);
53
void janus_echotest_incoming_rtp(janus_pluginession *handle, int video, char *buf, int len);
54
void janus_echotest_incoming_rtcp(janus_pluginession *handle, int video, char *buf, int len);
55
void janus_echotest_hangup_media(janus_pluginession *handle);
56
void janus_echotest_destroy_session(janus_pluginession *handle, int *error);
57

    
58
/* Plugin setup */
59
static janus_plugin janus_echotest_plugin =
60
        {
61
                .init = janus_echotest_init,
62
                .destroy = janus_echotest_destroy,
63

    
64
                .get_version = janus_echotest_get_version,
65
                .get_version_string = janus_echotest_get_version_string,
66
                .get_description = janus_echotest_get_description,
67
                .get_name = janus_echotest_get_name,
68
                .get_package = janus_echotest_get_package,
69
                
70
                .create_session = janus_echotest_create_session,
71
                .handle_message = janus_echotest_handle_message,
72
                .setup_media = janus_echotest_setup_media,
73
                .incoming_rtp = janus_echotest_incoming_rtp,
74
                .incoming_rtcp = janus_echotest_incoming_rtcp,
75
                .hangup_media = janus_echotest_hangup_media,
76
                .destroy_session = janus_echotest_destroy_session,
77
        }; 
78

    
79
/* Plugin creator */
80
janus_plugin *create(void) {
81
        JANUS_PRINT("%s created!\n", JANUS_ECHOTEST_NAME);
82
        return &janus_echotest_plugin;
83
}
84

    
85

    
86
/* Useful stuff */
87
static int initialized = 0, stopping = 0;
88
static janus_callbacks *gateway = NULL;
89
static GThread *handler_thread;
90
static void *janus_echotest_handler(void *data);
91

    
92
typedef struct janus_echotest_message {
93
        janus_pluginession *handle;
94
        char *transaction;
95
        char *message;
96
        char *sdp_type;
97
        char *sdp;
98
} janus_echotest_message;
99
GQueue *messages;
100

    
101
typedef struct janus_echotest_session {
102
        janus_pluginession *handle;
103
        gboolean audio_active;
104
        gboolean video_active;
105
        uint64_t bitrate;
106
        gboolean destroy;
107
} janus_echotest_session;
108
GHashTable *sessions;
109

    
110

    
111
/* Plugin implementation */
112
int janus_echotest_init(janus_callbacks *callback, const char *config_path) {
113
        if(stopping) {
114
                /* Still stopping from before */
115
                return -1;
116
        }
117
        if(callback == NULL || config_path == NULL) {
118
                /* Invalid arguments */
119
                return -1;
120
        }
121

    
122
        /* Read configuration */
123
        char filename[255];
124
        sprintf(filename, "%s/%s.cfg", config_path, JANUS_ECHOTEST_PACKAGE);
125
        JANUS_PRINT("Configuration file: %s\n", filename);
126
        janus_config *config = janus_config_parse(filename);
127
        if(config != NULL)
128
                janus_config_print(config);
129
        /* This plugin actually has nothing to configure... */
130
        janus_config_destroy(config);
131
        config = NULL;
132
        
133
        sessions = g_hash_table_new(NULL, NULL);
134
        messages = g_queue_new();
135
        /* This is the callback we'll need to invoke to contact the gateway */
136
        gateway = callback;
137

    
138
        initialized = 1;
139
        /* Launch the thread that will handle incoming messages */
140
        GError *error = NULL;
141
        handler_thread = g_thread_try_new("janus echotest handler", janus_echotest_handler, NULL, &error);
142
        if(error != NULL) {
143
                initialized = 0;
144
                /* Something went wrong... */
145
                JANUS_DEBUG("Got error %d (%s) trying to launch thread...\n", error->code, error->message ? error->message : "??");
146
                return -1;
147
        }
148
        JANUS_PRINT("%s initialized!\n", JANUS_ECHOTEST_NAME);
149
        return 0;
150
}
151

    
152
void janus_echotest_destroy() {
153
        if(!initialized)
154
                return;
155
        stopping = 1;
156
        if(handler_thread != NULL) {
157
                g_thread_join(handler_thread);
158
        }
159
        handler_thread = NULL;
160
        g_hash_table_destroy(sessions);
161
        g_queue_free(messages);
162
        sessions = NULL;
163
        initialized = 0;
164
        stopping = 0;
165
        JANUS_PRINT("%s destroyed!\n", JANUS_ECHOTEST_NAME);
166
}
167

    
168
int janus_echotest_get_version() {
169
        return JANUS_ECHOTEST_VERSION;
170
}
171

    
172
const char *janus_echotest_get_version_string() {
173
        return JANUS_ECHOTEST_VERSION_STRING;
174
}
175

    
176
const char *janus_echotest_get_description() {
177
        return JANUS_ECHOTEST_DESCRIPTION;
178
}
179

    
180
const char *janus_echotest_get_name() {
181
        return JANUS_ECHOTEST_NAME;
182
}
183

    
184
const char *janus_echotest_get_package() {
185
        return JANUS_ECHOTEST_PACKAGE;
186
}
187

    
188
void janus_echotest_create_session(janus_pluginession *handle, int *error) {
189
        if(stopping || !initialized) {
190
                *error = -1;
191
                return;
192
        }        
193
        janus_echotest_session *session = (janus_echotest_session *)calloc(1, sizeof(janus_echotest_session));
194
        if(session == NULL) {
195
                JANUS_DEBUG("Memory error!\n");
196
                *error = -2;
197
                return;
198
        }
199
        session->handle = handle;
200
        session->audio_active = TRUE;
201
        session->video_active = TRUE;
202
        session->bitrate = 0;        /* No limit */
203
        handle->plugin_handle = session;
204
        g_hash_table_insert(sessions, handle, session);
205

    
206
        return;
207
}
208

    
209
void janus_echotest_destroy_session(janus_pluginession *handle, int *error) {
210
        if(stopping || !initialized) {
211
                *error = -1;
212
                return;
213
        }        
214
        janus_echotest_session *session = (janus_echotest_session *)handle->plugin_handle;
215
        if(!session) {
216
                JANUS_DEBUG("No session associated with this handle...\n");
217
                *error = -2;
218
                return;
219
        }
220
        JANUS_PRINT("Removing Echo Test session...\n");
221
        /* TODO Actually clean up and remove ongoing sessions */
222
        g_hash_table_remove(sessions, handle);
223
        session->destroy = TRUE;
224
        g_free(session);
225
        return;
226
}
227

    
228
void janus_echotest_handle_message(janus_pluginession *handle, char *transaction, char *message, char *sdp_type, char *sdp) {
229
        if(stopping || !initialized)
230
                return;
231
        JANUS_PRINT("%s\n", message);
232
        janus_echotest_message *msg = calloc(1, sizeof(janus_echotest_message));
233
        if(msg == NULL) {
234
                JANUS_DEBUG("Memory error!\n");
235
                return;
236
        }
237
        msg->handle = handle;
238
        msg->transaction = transaction ? g_strdup(transaction) : NULL;
239
        msg->message = message;
240
        msg->sdp_type = sdp_type;
241
        msg->sdp = sdp;
242
        g_queue_push_tail(messages, msg);
243
}
244

    
245
void janus_echotest_setup_media(janus_pluginession *handle) {
246
        JANUS_DEBUG("WebRTC media is now available\n");
247
        if(stopping || !initialized)
248
                return;
249
        janus_echotest_session *session = (janus_echotest_session *)handle->plugin_handle;        
250
        if(!session) {
251
                JANUS_DEBUG("No session associated with this handle...\n");
252
                return;
253
        }
254
        if(session->destroy)
255
                return;
256
        /* We really don't care, as we only send RTP/RTCP we get in the first place back anyway */
257
}
258

    
259
void janus_echotest_incoming_rtp(janus_pluginession *handle, int video, char *buf, int len) {
260
        if(stopping || !initialized)
261
                return;
262
        /* Simple echo test */
263
        if(gateway) {
264
                /* Honour the audio/video active flags */
265
                janus_echotest_session *session = (janus_echotest_session *)handle->plugin_handle;        
266
                if(!session) {
267
                        JANUS_DEBUG("No session associated with this handle...\n");
268
                        return;
269
                }
270
                if(session->destroy)
271
                        return;
272
                if((!video && session->audio_active) || (video && session->video_active)) {
273
                        gateway->relay_rtp(handle, video, buf, len);
274
                }
275
        }
276
}
277

    
278
void janus_echotest_incoming_rtcp(janus_pluginession *handle, int video, char *buf, int len) {
279
        if(stopping || !initialized)
280
                return;
281
        /* Simple echo test */
282
        if(gateway) {
283
                janus_echotest_session *session = (janus_echotest_session *)handle->plugin_handle;        
284
                if(!session) {
285
                        JANUS_DEBUG("No session associated with this handle...\n");
286
                        return;
287
                }
288
                if(session->destroy)
289
                        return;
290
                if(session->bitrate > 0)
291
                        janus_rtcp_cap_remb(buf, len, session->bitrate);
292
                gateway->relay_rtcp(handle, video, buf, len);
293
        }
294
}
295

    
296
void janus_echotest_hangup_media(janus_pluginession *handle) {
297
        JANUS_PRINT("No WebRTC media anymore\n");
298
        if(stopping || !initialized)
299
                return;
300
        janus_echotest_session *session = (janus_echotest_session *)handle->plugin_handle;
301
        if(!session) {
302
                JANUS_DEBUG("No session associated with this handle...\n");
303
                return;
304
        }
305
        if(session->destroy)
306
                return;
307
        /* Send an event to the browser and tell it's over */
308
        json_t *event = json_object();
309
        json_object_set(event, "echotest", json_string("event"));
310
        json_object_set(event, "result", json_string("done"));
311
        char *event_text = json_dumps(event, JSON_INDENT(3));
312
        json_decref(event);
313
        JANUS_PRINT("Pushing event: %s\n", event_text);
314
        JANUS_PRINT("  >> %d\n", gateway->push_event(handle, &janus_echotest_plugin, NULL, event_text, NULL, NULL));
315
        /* Reset controls */
316
        session->audio_active = TRUE;
317
        session->video_active = TRUE;
318
        session->bitrate = 0;
319
}
320

    
321
/* Thread to handle incoming messages */
322
static void *janus_echotest_handler(void *data) {
323
        JANUS_DEBUG("Joining thread\n");
324
        janus_echotest_message *msg = NULL;
325
        char *error_cause = calloc(512, sizeof(char));        /* FIXME 512 should be enough, but anyway... */
326
        if(error_cause == NULL) {
327
                JANUS_DEBUG("Memory error!\n");
328
                return NULL;
329
        }
330
        while(initialized && !stopping) {
331
                if(!messages || (msg = g_queue_pop_head(messages)) == NULL) {
332
                        usleep(50000);
333
                        continue;
334
                }
335
                janus_echotest_session *session = (janus_echotest_session *)msg->handle->plugin_handle;
336
                if(!session) {
337
                        JANUS_DEBUG("No session associated with this handle...\n");
338
                        continue;
339
                }
340
                if(session->destroy)
341
                        continue;
342
                /* Handle request */
343
                JANUS_PRINT("Handling message: %s\n", msg->message);
344
                if(msg->message == NULL) {
345
                        JANUS_DEBUG("No message??\n");
346
                        sprintf(error_cause, "%s", "No message??");
347
                        goto error;
348
                }
349
                json_error_t error;
350
                json_t *root = json_loads(msg->message, 0, &error);
351
                if(!root) {
352
                        JANUS_DEBUG("JSON error: on line %d: %s\n", error.line, error.text);
353
                        sprintf(error_cause, "JSON error: on line %d: %s", error.line, error.text);
354
                        goto error;
355
                }
356
                if(!json_is_object(root)) {
357
                        JANUS_DEBUG("JSON error: not an object\n");
358
                        sprintf(error_cause, "JSON error: not an object");
359
                        goto error;
360
                }
361
                json_t *audio = json_object_get(root, "audio");
362
                if(audio && !json_is_boolean(audio)) {
363
                        JANUS_DEBUG("JSON error: invalid element (audio)\n");
364
                        sprintf(error_cause, "JSON error: invalid value (audio)");
365
                        goto error;
366
                }
367
                json_t *video = json_object_get(root, "video");
368
                if(video && !json_is_boolean(video)) {
369
                        JANUS_DEBUG("JSON error: invalid element (video)\n");
370
                        sprintf(error_cause, "JSON error: invalid value (video)");
371
                        goto error;
372
                }
373
                json_t *bitrate = json_object_get(root, "bitrate");
374
                if(bitrate && !json_is_integer(bitrate)) {
375
                        JANUS_DEBUG("JSON error: invalid element (bitrate)\n");
376
                        sprintf(error_cause, "JSON error: invalid value (bitrate)");
377
                        goto error;
378
                }
379
                if(audio) {
380
                        session->audio_active = json_is_true(audio);
381
                        JANUS_PRINT("Setting audio property: %s\n", session->audio_active ? "true" : "false");
382
                }
383
                if(video) {
384
                        session->video_active = json_is_true(video);
385
                        JANUS_PRINT("Setting video property: %s\n", session->video_active ? "true" : "false");
386
                }
387
                if(bitrate) {
388
                        session->bitrate = json_integer_value(bitrate);
389
                        JANUS_PRINT("Setting video bitrate: %"SCNu64"\n", session->bitrate);
390
                        if(session->bitrate > 0) {
391
                                /* FIXME Generate a new REMB (especially useful for Firefox, which doesn't send any we can cap later) */
392
                                char buf[24];
393
                                memset(buf, 0, 24);
394
                                janus_rtcp_remb((char *)&buf, 24, session->bitrate);
395
                                JANUS_PRINT("Sending REMB\n");
396
                                gateway->relay_rtcp(session->handle, 1, buf, 24);
397
                                /* FIXME How should we handle a subsequent "no limit" bitrate? */
398
                        }
399
                }
400
                /* Any SDP to handle? */
401
                if(msg->sdp) {
402
                        JANUS_PRINT("This is involving a negotiation (%s) as well:\n%s\n", msg->sdp_type, msg->sdp);
403
                }
404

    
405
                /* Prepare JSON event */
406
                json_t *event = json_object();
407
                json_object_set(event, "echotest", json_string("event"));
408
                json_object_set(event, "result", json_string("ok"));
409
                char *event_text = json_dumps(event, JSON_INDENT(3));
410
                json_decref(event);
411
                JANUS_PRINT("Pushing event: %s\n", event_text);
412
                if(!msg->sdp) {
413
                        JANUS_PRINT("  >> %d\n", gateway->push_event(msg->handle, &janus_echotest_plugin, msg->transaction, event_text, NULL, NULL));
414
                } else {
415
                        /* Forward the same offer to the gateway, to start the echo test */
416
                        char *type = NULL;
417
                        if(!strcasecmp(msg->sdp_type, "offer"))
418
                                type = "answer";
419
                        if(!strcasecmp(msg->sdp_type, "answer"))
420
                                type = "offer";
421
                        /* How long will the gateway take to push the event? */
422
                        gint64 start = janus_get_monotonic_time();
423
                        int res = gateway->push_event(msg->handle, &janus_echotest_plugin, msg->transaction, event_text, type, msg->sdp);
424
                        JANUS_PRINT("  >> Pushing event: %d (took %"SCNu64" ms)\n",
425
                                res, janus_get_monotonic_time()-start);
426
                }
427
                continue;
428
                
429
error:
430
                {
431
                        if(root != NULL)
432
                                json_decref(root);
433
                        /* Prepare JSON error event */
434
                        json_t *event = json_object();
435
                        json_object_set(event, "echotest", json_string("event"));
436
                        json_object_set(event, "error", json_string(error_cause));
437
                        char *event_text = json_dumps(event, JSON_INDENT(3));
438
                        json_decref(event);
439
                        JANUS_PRINT("Pushing event: %s\n", event_text);
440
                        JANUS_PRINT("  >> %d\n", gateway->push_event(msg->handle, &janus_echotest_plugin, msg->transaction, event_text, NULL, NULL));
441
                }
442
        }
443
        JANUS_DEBUG("Leaving thread\n");
444
        return NULL;
445
}