Statistics
| Branch: | Revision:

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

History | View | Annotate | Download (22.4 KB)

1
/*! \file   janus_voicemail.c
2
 * \author Lorenzo Miniero <lorenzo@meetecho.com>
3
 * \copyright GNU Affero General Public License v3
4
 * \brief  Janus VoiceMail plugin
5
 * \details  This is a plugin implementing a very simple VoiceMail service
6
 * for Janus, specifically recording Opus streams. This means that it replies
7
 * by providing in the SDP only support for Opus, and disabling video.
8
 * When a peer contacts the plugin, the plugin starts recording the audio
9
 * frames it receives and, after 10 seconds, it shuts the PeerConnection
10
 * down and returns an URL to the recorded file.
11
 * 
12
 * Since an URL is returned, the plugin allows you to configure where the
13
 * recordings whould be stored (e.g., a folder in your web server, writable
14
 * by the plugin) and the base path to use when returning URLs (e.g.,
15
 * /my/recordings/ or http://www.example.com/my/recordings).
16
 * 
17
 * By default the plugin saves the recordings in the \c html folder of
18
 * this project, meaning that it can work out of the box with the VoiceMail
19
 * demo we provide in the same folder.
20
 *
21
 * \ingroup plugins
22
 * \ref plugins
23
 */
24

    
25
#include "plugin.h"
26

    
27
#include <jansson.h>
28
#include <sys/stat.h>
29
#include <sys/time.h>
30

    
31
#include <ogg/ogg.h>
32

    
33
#include "../config.h"
34
#include "../rtp.h"
35
#include "../utils.h"
36

    
37

    
38
/* Plugin information */
39
#define JANUS_VOICEMAIL_VERSION                        1
40
#define JANUS_VOICEMAIL_VERSION_STRING        "0.0.1"
41
#define JANUS_VOICEMAIL_DESCRIPTION                "This is a plugin implementing a very simple VoiceMail service for Janus, recording Opus streams."
42
#define JANUS_VOICEMAIL_NAME                                "JANUS VoiceMail plugin"
43
#define JANUS_VOICEMAIL_PACKAGE                        "janus.plugin.voicemail"
44

    
45
/* Plugin methods */
46
janus_plugin *create(void);
47
int janus_voicemail_init(janus_callbacks *callback, const char *config_path);
48
void janus_voicemail_destroy(void);
49
int janus_voicemail_get_version(void);
50
const char *janus_voicemail_get_version_string(void);
51
const char *janus_voicemail_get_description(void);
52
const char *janus_voicemail_get_name(void);
53
const char *janus_voicemail_get_package(void);
54
void janus_voicemail_create_session(janus_pluginession *handle, int *error);
55
void janus_voicemail_handle_message(janus_pluginession *handle, char *transaction, char *message, char *sdp_type, char *sdp);
56
void janus_voicemail_setup_media(janus_pluginession *handle);
57
void janus_voicemail_incoming_rtp(janus_pluginession *handle, int video, char *buf, int len);
58
void janus_voicemail_incoming_rtcp(janus_pluginession *handle, int video, char *buf, int len);
59
void janus_voicemail_hangup_media(janus_pluginession *handle);
60
void janus_voicemail_destroy_session(janus_pluginession *handle, int *error);
61

    
62
/* Plugin setup */
63
static janus_plugin janus_voicemail_plugin =
64
        {
65
                .init = janus_voicemail_init,
66
                .destroy = janus_voicemail_destroy,
67

    
68
                .get_version = janus_voicemail_get_version,
69
                .get_version_string = janus_voicemail_get_version_string,
70
                .get_description = janus_voicemail_get_description,
71
                .get_name = janus_voicemail_get_name,
72
                .get_package = janus_voicemail_get_package,
73
                
74
                .create_session = janus_voicemail_create_session,
75
                .handle_message = janus_voicemail_handle_message,
76
                .setup_media = janus_voicemail_setup_media,
77
                .incoming_rtp = janus_voicemail_incoming_rtp,
78
                .incoming_rtcp = janus_voicemail_incoming_rtcp,
79
                .hangup_media = janus_voicemail_hangup_media,
80
                .destroy_session = janus_voicemail_destroy_session,
81
        }; 
82

    
83
/* Plugin creator */
84
janus_plugin *create(void) {
85
        JANUS_PRINT("%s created!\n", JANUS_VOICEMAIL_NAME);
86
        return &janus_voicemail_plugin;
87
}
88

    
89

    
90
/* Useful stuff */
91
static int initialized = 0, stopping = 0;
92
static janus_callbacks *gateway = NULL;
93
static GThread *handler_thread;
94
static void *janus_voicemail_handler(void *data);
95

    
96
typedef struct janus_voicemail_message {
97
        janus_pluginession *handle;
98
        char *transaction;
99
        char *message;
100
        char *sdp_type;
101
        char *sdp;
102
} janus_voicemail_message;
103
GQueue *messages;
104

    
105
typedef struct janus_voicemail_session {
106
        janus_pluginession *handle;
107
        guint64 recording_id;
108
        gint64 start_time;
109
        char *filename;
110
        FILE *file;
111
        ogg_stream_state *stream;
112
        int seq;
113
        gboolean started;
114
        gboolean stopping;
115
        gboolean destroy;
116
} janus_voicemail_session;
117
GHashTable *sessions;
118

    
119
static char *recordings_path = NULL;
120
static char *recordings_base = NULL;
121

    
122
/* SDP offer/answer template */
123
static const char *sdp_template =
124
                "v=0\r\n"
125
                "o=- %"SCNu64" %"SCNu64" IN IP4 127.0.0.1\r\n"        /* We need current time here */
126
                "s=VoiceMail %"SCNu64"\r\n"                                                /* VoiceMail recording ID */
127
                "t=0 0\r\n"
128
                "m=audio 1 RTP/SAVPF %d\r\n"                /* Opus payload type */
129
                "c=IN IP4 1.1.1.1\r\n"
130
                "a=rtpmap:%d opus/48000/2\r\n"                /* Opus payload type */
131
                "a=mid:audio\r\n"
132
                "a=recvonly\r\n";                                        /* This plugin doesn't send any frames */
133

    
134

    
135
/* OGG/Opus helpers */
136
void le32(unsigned char *p, int v);
137
void le16(unsigned char *p, int v);
138
ogg_packet *op_opushead(void);
139
ogg_packet *op_opustags(void);
140
ogg_packet *op_from_pkt(const unsigned char *pkt, int len);
141
void op_free(ogg_packet *op);
142
int ogg_write(janus_voicemail_session *session);
143
int ogg_flush(janus_voicemail_session *session);
144

    
145

    
146
/* Plugin implementation */
147
int janus_voicemail_init(janus_callbacks *callback, const char *config_path) {
148
        if(stopping) {
149
                /* Still stopping from before */
150
                return -1;
151
        }
152
        if(callback == NULL || config_path == NULL) {
153
                /* Invalid arguments */
154
                return -1;
155
        }
156

    
157
        /* Read configuration */
158
        char filename[255];
159
        sprintf(filename, "%s/%s.cfg", config_path, JANUS_VOICEMAIL_PACKAGE);
160
        JANUS_PRINT("Configuration file: %s\n", filename);
161
        janus_config *config = janus_config_parse(filename);
162
        if(config != NULL)
163
                janus_config_print(config);
164
        
165
        sessions = g_hash_table_new(NULL, NULL);
166
        messages = g_queue_new();
167
        /* This is the callback we'll need to invoke to contact the gateway */
168
        gateway = callback;
169

    
170
        /* Parse configuration */
171
        if(config != NULL) {
172
                janus_config_item *path = janus_config_get_item_drilldown(config, "general", "path");
173
                if(path && path->value)
174
                        recordings_path = g_strdup(path->value);
175
                janus_config_item *base = janus_config_get_item_drilldown(config, "general", "base");
176
                if(base && base->value)
177
                        recordings_base = g_strdup(base->value);
178
                /* Done */
179
                janus_config_destroy(config);
180
                config = NULL;
181
        }
182
        if(recordings_path == NULL)
183
                recordings_path = "./html/recordings/";
184
        if(recordings_base == NULL)
185
                recordings_base = "/recordings/";
186
        JANUS_PRINT("Recordings path: %s\n", recordings_path);
187
        JANUS_PRINT("Recordings base: %s\n", recordings_base);
188
        /* Create the folder, if needed */
189
        struct stat st = {0};
190
        if(stat(recordings_path, &st) == -1) {
191
                int res = mkdir(recordings_path, 0755);
192
                JANUS_PRINT("Creating folder: %d\n", res);
193
                if(res < 0) {
194
                        JANUS_DEBUG("%s", strerror(res));
195
                        return -1;        /* No point going on... */
196
                }
197
        }
198
        
199
        initialized = 1;
200
        /* Launch the thread that will handle incoming messages */
201
        GError *error = NULL;
202
        handler_thread = g_thread_try_new("janus voicemail handler", janus_voicemail_handler, NULL, &error);
203
        if(error != NULL) {
204
                initialized = 0;
205
                /* Something went wrong... */
206
                JANUS_DEBUG("Got error %d (%s) trying to launch thread...\n", error->code, error->message ? error->message : "??");
207
                return -1;
208
        }
209
        JANUS_PRINT("%s initialized!\n", JANUS_VOICEMAIL_NAME);
210
        return 0;
211
}
212

    
213
void janus_voicemail_destroy() {
214
        if(!initialized)
215
                return;
216
        stopping = 1;
217
        if(handler_thread != NULL) {
218
                g_thread_join(handler_thread);
219
        }
220
        handler_thread = NULL;
221
        /* Actually clean up and remove ongoing sessions */
222
        g_hash_table_destroy(sessions);
223
        g_queue_free(messages);
224
        sessions = NULL;
225
        initialized = 0;
226
        JANUS_PRINT("%s destroyed!\n", JANUS_VOICEMAIL_NAME);
227
}
228

    
229
int janus_voicemail_get_version() {
230
        return JANUS_VOICEMAIL_VERSION;
231
}
232

    
233
const char *janus_voicemail_get_version_string() {
234
        return JANUS_VOICEMAIL_VERSION_STRING;
235
}
236

    
237
const char *janus_voicemail_get_description() {
238
        return JANUS_VOICEMAIL_DESCRIPTION;
239
}
240

    
241
const char *janus_voicemail_get_name() {
242
        return JANUS_VOICEMAIL_NAME;
243
}
244

    
245
const char *janus_voicemail_get_package() {
246
        return JANUS_VOICEMAIL_PACKAGE;
247
}
248

    
249
void janus_voicemail_create_session(janus_pluginession *handle, int *error) {
250
        if(stopping || !initialized) {
251
                *error = -1;
252
                return;
253
        }        
254
        janus_voicemail_session *session = (janus_voicemail_session *)calloc(1, sizeof(janus_voicemail_session));
255
        if(session == NULL) {
256
                JANUS_DEBUG("Memory error!\n");
257
                *error = -2;
258
                return;
259
        }
260
        session->handle = handle;
261
        session->recording_id = g_random_int();
262
        session->start_time = 0;
263
        session->stream = NULL;
264
        char f[255];
265
        sprintf(f, "%s/janus-voicemail-%"SCNu64".opus", recordings_path, session->recording_id);
266
        session->filename = g_strdup(f);
267
        if(session->filename == NULL) {
268
                JANUS_DEBUG("Memory error!\n");
269
                *error = -2;
270
                return;
271
        }
272
        session->file = NULL;
273
        session->seq = 0;
274
        session->started = FALSE;
275
        session->stopping = FALSE;
276
        session->destroy = FALSE;
277
        handle->plugin_handle = session;
278
        g_hash_table_insert(sessions, handle, session);
279

    
280
        return;
281
}
282

    
283
void janus_voicemail_destroy_session(janus_pluginession *handle, int *error) {
284
        if(stopping || !initialized) {
285
                *error = -1;
286
                return;
287
        }        
288
        janus_voicemail_session *session = (janus_voicemail_session *)handle->plugin_handle; 
289
        if(!session) {
290
                JANUS_DEBUG("No session associated with this handle...\n");
291
                *error = -2;
292
                return;
293
        }
294
        if(session->destroy) {
295
                JANUS_PRINT("Session already destroyed...\n");
296
                g_free(session);
297
                return;
298
        }
299
        JANUS_PRINT("Removing VoiceMail session...\n");
300
        g_hash_table_remove(sessions, handle);
301
        janus_voicemail_hangup_media(handle);
302
        session->destroy = TRUE;
303
        g_free(session);
304

    
305
        return;
306
}
307

    
308
void janus_voicemail_handle_message(janus_pluginession *handle, char *transaction, char *message, char *sdp_type, char *sdp) {
309
        if(stopping || !initialized)
310
                return;
311
        JANUS_PRINT("%s\n", message);
312
        janus_voicemail_message *msg = calloc(1, sizeof(janus_voicemail_message));
313
        if(msg == NULL) {
314
                JANUS_DEBUG("Memory error!\n");
315
                return;
316
        }
317
        msg->handle = handle;
318
        msg->transaction = transaction ? g_strdup(transaction) : NULL;
319
        msg->message = message;
320
        msg->sdp_type = sdp_type;
321
        msg->sdp = sdp;
322
        g_queue_push_tail(messages, msg);
323
}
324

    
325
void janus_voicemail_setup_media(janus_pluginession *handle) {
326
        JANUS_DEBUG("WebRTC media is now available\n");
327
        if(stopping || !initialized)
328
                return;
329
        janus_voicemail_session *session = (janus_voicemail_session *)handle->plugin_handle;        
330
        if(!session) {
331
                JANUS_DEBUG("No session associated with this handle...\n");
332
                return;
333
        }
334
        if(session->destroy)
335
                return;
336
        /* Only start recording this peer when we get this event */
337
        session->start_time = janus_get_monotonic_time();
338
        session->started = TRUE;
339
        /* Prepare JSON event */
340
        json_t *event = json_object();
341
        json_object_set(event, "voicemail", json_string("event"));
342
        json_object_set(event, "status", json_string("started"));
343
        char *event_text = json_dumps(event, JSON_INDENT(3));
344
        json_decref(event);
345
        JANUS_PRINT("Pushing event: %s\n", event_text);
346
        JANUS_PRINT("  >> %d\n", gateway->push_event(handle, &janus_voicemail_plugin, NULL, event_text, NULL, NULL));
347
}
348

    
349
void janus_voicemail_incoming_rtp(janus_pluginession *handle, int video, char *buf, int len) {
350
        if(stopping || !initialized)
351
                return;
352
        janus_voicemail_session *session = (janus_voicemail_session *)handle->plugin_handle;        
353
        if(!session || session->destroy || session->stopping || !session->started || session->start_time == 0)
354
                return;
355
        gint64 now = janus_get_monotonic_time();
356
        /* Have 10 seconds passed? */
357
        if((now-session->start_time) >= 10*G_USEC_PER_SEC) {
358
                /* FIXME Simulate a "stop" coming from the browser */
359
                session->started = FALSE;
360
                janus_voicemail_message *msg = calloc(1, sizeof(janus_voicemail_message));
361
                if(msg == NULL) {
362
                        JANUS_DEBUG("Memory error!\n");
363
                        return;
364
                }
365
                msg->handle = handle;
366
                msg->message = "{\"request\":\"stop\"}";
367
                msg->transaction = NULL;
368
                msg->sdp_type = NULL;
369
                msg->sdp = NULL;
370
                g_queue_push_tail(messages, msg);
371
                return;
372
        }
373
        /* Save the frame */
374
        rtp_header *rtp = (rtp_header *)buf;
375
        uint16_t seq = ntohs(rtp->seq_number);
376
        if(session->seq == 0)
377
                session->seq = seq;
378
        ogg_packet *op = op_from_pkt((const unsigned char *)(buf+12), len-12);        /* TODO Check RTP extensions... */
379
        //~ JANUS_PRINT("\tWriting at position %d (%d)\n", seq-session->seq+1, 960*(seq-session->seq+1));
380
        op->granulepos = 960*(seq-session->seq+1); // FIXME: get this from the toc byte
381
        ogg_stream_packetin(session->stream, op);
382
        free(op);
383
        ogg_write(session);
384
}
385

    
386
void janus_voicemail_incoming_rtcp(janus_pluginession *handle, int video, char *buf, int len) {
387
        if(stopping || !initialized)
388
                return;
389
        /* FIXME Should we care? */
390
}
391

    
392
void janus_voicemail_hangup_media(janus_pluginession *handle) {
393
        JANUS_PRINT("No WebRTC media anymore\n");
394
        if(stopping || !initialized)
395
                return;
396
        janus_voicemail_session *session = (janus_voicemail_session *)handle->plugin_handle;
397
        if(!session) {
398
                JANUS_DEBUG("No session associated with this handle...\n");
399
                return;
400
        }
401
        if(session->destroy)
402
                return;
403
        session->started = FALSE;
404
        session->destroy = 1;
405
        /* Close and reset stuff */
406
        if(session->file)
407
                fclose(session->file);
408
        session->file = NULL;
409
        if(session->stream)
410
                ogg_stream_destroy(session->stream);
411
        session->stream = NULL;
412
}
413

    
414
/* Thread to handle incoming messages */
415
static void *janus_voicemail_handler(void *data) {
416
        JANUS_DEBUG("Joining thread\n");
417
        janus_voicemail_message *msg = NULL;
418
        char *error_cause = calloc(512, sizeof(char));        /* FIXME 512 should be enough, but anyway... */
419
        if(error_cause == NULL) {
420
                JANUS_DEBUG("Memory error!\n");
421
                return NULL;
422
        }
423
        while(initialized && !stopping) {
424
                if(!messages || (msg = g_queue_pop_head(messages)) == NULL) {
425
                        usleep(50000);
426
                        continue;
427
                }
428
                janus_voicemail_session *session = (janus_voicemail_session *)msg->handle->plugin_handle;        
429
                if(!session) {
430
                        JANUS_DEBUG("No session associated with this handle...\n");
431
                        continue;
432
                }
433
                if(session->destroy)
434
                        continue;
435
                /* Handle request */
436
                JANUS_PRINT("Handling message: %s\n", msg->message);
437
                if(msg->message == NULL) {
438
                        JANUS_DEBUG("No message??\n");
439
                        sprintf(error_cause, "%s", "No message??");
440
                        goto error;
441
                }
442
                json_error_t error;
443
                json_t *root = json_loads(msg->message, 0, &error);
444
                if(!root) {
445
                        JANUS_DEBUG("JSON error: on line %d: %s\n", error.line, error.text);
446
                        sprintf(error_cause, "JSON error: on line %d: %s", error.line, error.text);
447
                        goto error;
448
                }
449
                if(!json_is_object(root)) {
450
                        JANUS_DEBUG("JSON error: not an object\n");
451
                        sprintf(error_cause, "JSON error: not an object");
452
                        goto error;
453
                }
454
                /* Get the request first */
455
                json_t *request = json_object_get(root, "request");
456
                if(!request || !json_is_string(request)) {
457
                        JANUS_DEBUG("JSON error: invalid element (request)\n");
458
                        sprintf(error_cause, "JSON error: invalid element (request)");
459
                        goto error;
460
                }
461
                const char *request_text = json_string_value(request);
462
                json_t *event = NULL;
463
                if(!strcasecmp(request_text, "record")) {
464
                        JANUS_PRINT("Starting new recording\n");
465
                        if(session->file != NULL) {
466
                                JANUS_DEBUG("Already recording (%s)\n", session->filename ? session->filename : "??");
467
                                sprintf(error_cause, "Already recording");
468
                                goto error;
469
                        }
470
                        session->stream = malloc(sizeof(ogg_stream_state));
471
                        if(session->stream == NULL) {
472
                                JANUS_DEBUG("Couldn't allocate stream struct\n");
473
                                sprintf(error_cause, "Couldn't allocate stream struct");
474
                                goto error;
475
                        }
476
                        if(ogg_stream_init(session->stream, rand()) < 0) {
477
                                JANUS_DEBUG("Couldn't initialize Ogg stream state\n");
478
                                sprintf(error_cause, "Couldn't initialize Ogg stream state\n");
479
                                goto error;
480
                        }
481
                        session->file = fopen(session->filename, "wb");
482
                        if(session->file == NULL) {
483
                                JANUS_DEBUG("Couldn't open output file\n");
484
                                sprintf(error_cause, "Couldn't open output file");
485
                                goto error;
486
                        }
487
                        session->seq = 0;
488
                        /* Write stream headers */
489
                        ogg_packet *op = op_opushead();
490
                        ogg_stream_packetin(session->stream, op);
491
                        op_free(op);
492
                        op = op_opustags();
493
                        ogg_stream_packetin(session->stream, op);
494
                        op_free(op);
495
                        ogg_flush(session);
496
                        /* Done: now wait for the setup_media callback to be called */
497
                        event = json_object();
498
                        json_object_set(event, "voicemail", json_string("event"));
499
                        json_object_set(event, "status", json_string(session->started ? "started" : "starting"));
500
                } else if(!strcasecmp(request_text, "stop")) {
501
                        /* Stop the recording */
502
                        session->started = FALSE;
503
                        if(session->file)
504
                                fclose(session->file);
505
                        session->file = NULL;
506
                        if(session->stream)
507
                                ogg_stream_destroy(session->stream);
508
                        session->stream = NULL;
509
                        /* Done: now wait for the setup_media callback to be called */
510
                        event = json_object();
511
                        json_object_set(event, "voicemail", json_string("event"));
512
                        json_object_set(event, "status", json_string("done"));
513
                        char url[1024];
514
                        sprintf(url, "%s/janus-voicemail-%"SCNu64".opus", recordings_base, session->recording_id);
515
                        json_object_set(event, "recording", json_string(url));
516
                } else {
517
                        JANUS_DEBUG("Unknown request '%s'\n", request_text);
518
                        sprintf(error_cause, "Unknown request '%s'", request_text);
519
                        goto error;
520
                }
521

    
522
                /* Prepare JSON event */
523
                JANUS_PRINT("Preparing JSON event as a reply\n");
524
                char *event_text = json_dumps(event, JSON_INDENT(3));
525
                json_decref(event);
526
                /* Any SDP to handle? */
527
                if(!msg->sdp) {
528
                        JANUS_PRINT("  >> %d\n", gateway->push_event(msg->handle, &janus_voicemail_plugin, msg->transaction, event_text, NULL, NULL));
529
                } else {
530
                        JANUS_PRINT("This is involving a negotiation (%s) as well:\n%s\n", msg->sdp_type, msg->sdp);
531
                        char *type = NULL;
532
                        if(!strcasecmp(msg->sdp_type, "offer"))
533
                                type = "answer";
534
                        if(!strcasecmp(msg->sdp_type, "answer"))
535
                                type = "offer";
536
                        /* Fill the SDP template and use that as our answer */
537
                        char sdp[1024];
538
                        /* What is the Opus payload type? */
539
                        int opus_pt = 0;
540
                        char *fmtp = strstr(msg->sdp, "opus/48000");
541
                        if(fmtp != NULL) {
542
                                fmtp -= 5;
543
                                fmtp = strstr(fmtp, ":");
544
                                if(fmtp)
545
                                        fmtp++;
546
                                opus_pt = atoi(fmtp);
547
                        }
548
                        JANUS_PRINT("Opus payload type is %d\n", opus_pt);
549
                        g_sprintf(sdp, sdp_template,
550
                                janus_get_monotonic_time(),                /* We need current time here */
551
                                janus_get_monotonic_time(),                /* We need current time here */
552
                                session->recording_id,                        /* Recording ID */
553
                                opus_pt,                                                /* Opus payload type */
554
                                opus_pt                                                        /* Opus payload type */);
555
                        /* Did the peer negotiate video? */
556
                        if(strstr(msg->sdp, "m=video") != NULL) {
557
                                /* If so, reject it */
558
                                g_strlcat(sdp, "m=video 0 RTP/SAVPF 0\r\n", 1024);                                
559
                        }
560
                        /* How long will the gateway take to push the event? */
561
                        gint64 start = janus_get_monotonic_time();
562
                        int res = gateway->push_event(msg->handle, &janus_voicemail_plugin, msg->transaction, event_text, type, sdp);
563
                        JANUS_PRINT("  >> Pushing event: %d (took %"SCNu64" ms)\n", res, janus_get_monotonic_time()-start);
564
                        if(res != JANUS_OK) {
565
                                /* TODO Failed to negotiate? We should remove this participant */
566
                        }
567
                }
568

    
569
                continue;
570
                
571
error:
572
                {
573
                        if(root != NULL)
574
                                json_decref(root);
575
                        /* Prepare JSON error event */
576
                        json_t *event = json_object();
577
                        json_object_set(event, "voicemail", json_string("event"));
578
                        json_object_set(event, "error", json_string(error_cause));
579
                        char *event_text = json_dumps(event, JSON_INDENT(3));
580
                        json_decref(event);
581
                        JANUS_PRINT("Pushing event: %s\n", event_text);
582
                        JANUS_PRINT("  >> %d\n", gateway->push_event(msg->handle, &janus_voicemail_plugin, msg->transaction, event_text, NULL, NULL));
583
                }
584
        }
585
        JANUS_DEBUG("Leaving thread\n");
586
        return NULL;
587
}
588

    
589

    
590
/* OGG/Opus helpers */
591
/* Write a little-endian 32 bit int to memory */
592
void le32(unsigned char *p, int v) {
593
        p[0] = v & 0xff;
594
        p[1] = (v >> 8) & 0xff;
595
        p[2] = (v >> 16) & 0xff;
596
        p[3] = (v >> 24) & 0xff;
597
}
598

    
599

    
600
/* Write a little-endian 16 bit int to memory */
601
void le16(unsigned char *p, int v) {
602
        p[0] = v & 0xff;
603
        p[1] = (v >> 8) & 0xff;
604
}
605

    
606
/* ;anufacture a generic OpusHead packet */
607
ogg_packet *op_opushead() {
608
        int size = 19;
609
        unsigned char *data = malloc(size);
610
        ogg_packet *op = malloc(sizeof(*op));
611

    
612
        if(!data) {
613
                JANUS_DEBUG("Couldn't allocate data buffer...\n");
614
                return NULL;
615
        }
616
        if(!op) {
617
                JANUS_DEBUG("Couldn't allocate Ogg packet...\n");
618
                return NULL;
619
        }
620

    
621
        memcpy(data, "OpusHead", 8);  /* identifier */
622
        data[8] = 1;                  /* version */
623
        data[9] = 2;                  /* channels */
624
        le16(data+10, 0);             /* pre-skip */
625
        le32(data + 12, 48000);       /* original sample rate */
626
        le16(data + 16, 0);           /* gain */
627
        data[18] = 0;                 /* channel mapping family */
628

    
629
        op->packet = data;
630
        op->bytes = size;
631
        op->b_o_s = 1;
632
        op->e_o_s = 0;
633
        op->granulepos = 0;
634
        op->packetno = 0;
635

    
636
        return op;
637
}
638

    
639
/* Manufacture a generic OpusTags packet */
640
ogg_packet *op_opustags() {
641
        char *identifier = "OpusTags";
642
        char *vendor = "opus rtp packet dump";
643
        int size = strlen(identifier) + 4 + strlen(vendor) + 4;
644
        unsigned char *data = malloc(size);
645
        ogg_packet *op = malloc(sizeof(*op));
646

    
647
        if(!data) {
648
                JANUS_DEBUG("Couldn't allocate data buffer...\n");
649
                return NULL;
650
        }
651
        if(!op) {
652
                JANUS_DEBUG("Couldn't allocate Ogg packet...\n");
653
                return NULL;
654
        }
655

    
656
        memcpy(data, identifier, 8);
657
        le32(data + 8, strlen(vendor));
658
        memcpy(data + 12, vendor, strlen(vendor));
659
        le32(data + 12 + strlen(vendor), 0);
660

    
661
        op->packet = data;
662
        op->bytes = size;
663
        op->b_o_s = 0;
664
        op->e_o_s = 0;
665
        op->granulepos = 0;
666
        op->packetno = 1;
667

    
668
        return op;
669
}
670

    
671
/* Allocate an ogg_packet */
672
ogg_packet *op_from_pkt(const unsigned char *pkt, int len) {
673
        ogg_packet *op = malloc(sizeof(*op));
674
        if(!op) {
675
                JANUS_DEBUG("Couldn't allocate Ogg packet.\n");
676
                return NULL;
677
        }
678

    
679
        op->packet = (unsigned char *)pkt;
680
        op->bytes = len;
681
        op->b_o_s = 0;
682
        op->e_o_s = 0;
683

    
684
        return op;
685
}
686

    
687
/* Free a packet and its contents */
688
void op_free(ogg_packet *op) {
689
        if(op) {
690
                if(op->packet) {
691
                        free(op->packet);
692
                }
693
                free(op);
694
        }
695
}
696

    
697
/* Write out available ogg pages */
698
int ogg_write(janus_voicemail_session *session) {
699
        ogg_page page;
700
        size_t written;
701

    
702
        if(!session || !session->stream || !session->file) {
703
                return -1;
704
        }
705

    
706
        while (ogg_stream_pageout(session->stream, &page)) {
707
                written = fwrite(page.header, 1, page.header_len, session->file);
708
                if(written != (size_t)page.header_len) {
709
                        JANUS_DEBUG("Error writing Ogg page header\n");
710
                        return -2;
711
                }
712
                written = fwrite(page.body, 1, page.body_len, session->file);
713
                if(written != (size_t)page.body_len) {
714
                        JANUS_DEBUG("Error writing Ogg page body\n");
715
                        return -3;
716
                }
717
        }
718
        return 0;
719
}
720

    
721
/* Flush remaining ogg data */
722
int ogg_flush(janus_voicemail_session *session) {
723
        ogg_page page;
724
        size_t written;
725

    
726
        if(!session || !session->stream || !session->file) {
727
                return -1;
728
        }
729

    
730
        while (ogg_stream_flush(session->stream, &page)) {
731
                written = fwrite(page.header, 1, page.header_len, session->file);
732
                if(written != (size_t)page.header_len) {
733
                        JANUS_DEBUG("Error writing Ogg page header\n");
734
                        return -2;
735
                }
736
                written = fwrite(page.body, 1, page.body_len, session->file);
737
                if(written != (size_t)page.body_len) {
738
                        JANUS_DEBUG("Error writing Ogg page body\n");
739
                        return -3;
740
                }
741
        }
742
        return 0;
743
}