Statistics
| Branch: | Revision:

janus-gateway / record.c @ 226cceb4

History | View | Annotate | Download (9.36 KB)

1 9d11ac50 meetecho
/*! \file    record.h
2
 * \author   Lorenzo Miniero <lorenzo@meetecho.com>
3
 * \copyright GNU General Public License v3
4
 * \brief    Audio/Video recorder
5
 * \details  Implementation of a simple recorder utility that plugins
6
 * can make use of to record audio/video frames to a Janus file. This
7
 * file just saves RTP frames in a structured way, so that they can be
8
 * post-processed later on to get a valid container file (e.g., a .opus
9
 * file for Opus audio or a .webm file for VP8 video) and keep things
10
 * simpler on the plugin and core side.
11
 * \note If you want to record both audio and video, you'll have to use
12
 * two different recorders. Any muxing in the same container will have
13
 * to be done in the post-processing phase.
14
 * 
15
 * \ingroup core
16
 * \ref core
17
 */
18
 
19
#include <arpa/inet.h>
20
#include <sys/stat.h>
21
#include <errno.h>
22
23 6089eb4e Lorenzo Miniero
#include <glib.h>
24
#include <jansson.h>
25
26 9d11ac50 meetecho
#include "record.h"
27
#include "debug.h"
28 5fa9a305 meetecho
#include "utils.h"
29 9d11ac50 meetecho
30 ff2d3e1a Lorenzo Miniero
#define htonll(x) ((1==htonl(1)) ? (x) : ((gint64)htonl((x) & 0xFFFFFFFF) << 32) | htonl((x) >> 32))
31
#define ntohll(x) ((1==ntohl(1)) ? (x) : ((gint64)ntohl((x) & 0xFFFFFFFF) << 32) | ntohl((x) >> 32))
32
33 9d11ac50 meetecho
34 6089eb4e Lorenzo Miniero
/* Info header in the structured recording */
35
static const char *header = "MJR00001";
36
/* Frame header in the structured recording */
37
static const char *frame_header = "MEETECHO";
38 9d11ac50 meetecho
39 becc0c84 Lorenzo Miniero
/* Whether the filenames should have a temporary extension, while saving, or not (default=false) */
40
static gboolean rec_tempname = FALSE;
41
/* Extension to add in case tempnames is true (default="tmp" --> ".tmp") */
42
static char *rec_tempext = NULL;
43
44
void janus_recorder_init(gboolean tempnames, const char *extension) {
45
        JANUS_LOG(LOG_INFO, "Initializing recorder code\n");
46
        if(tempnames) {
47
                rec_tempname = TRUE;
48
                if(extension == NULL) {
49
                        rec_tempext = g_strdup("tmp");
50
                        JANUS_LOG(LOG_INFO, "  -- No extension provided, using default one (tmp)");
51
                } else {
52
                        rec_tempext = g_strdup(extension);
53
                        JANUS_LOG(LOG_INFO, "  -- Using temporary extension .%s", rec_tempext);
54
                }
55
        }
56
}
57
58
void janus_recorder_deinit(void) {
59
        rec_tempname = FALSE;
60
        g_free(rec_tempext);
61
}
62
63 9d11ac50 meetecho
64 b29447b1 Lorenzo Miniero
janus_recorder *janus_recorder_create(const char *dir, const char *codec, const char *filename) {
65 ff2d3e1a Lorenzo Miniero
        janus_recorder_medium type = JANUS_RECORDER_AUDIO;
66 b29447b1 Lorenzo Miniero
        if(codec == NULL) {
67
                JANUS_LOG(LOG_ERR, "Missing codec information\n");
68
                return NULL;
69
        }
70
        if(!strcasecmp(codec, "vp8") || !strcasecmp(codec, "vp9") || !strcasecmp(codec, "h264")) {
71 ff2d3e1a Lorenzo Miniero
                type = JANUS_RECORDER_VIDEO;
72 b29447b1 Lorenzo Miniero
        } else if(!strcasecmp(codec, "opus") || !strcasecmp(codec, "g711") || !strcasecmp(codec, "pcmu") || !strcasecmp(codec, "pcma")) {
73 ff2d3e1a Lorenzo Miniero
                type = JANUS_RECORDER_AUDIO;
74 b29447b1 Lorenzo Miniero
                if(!strcasecmp(codec, "pcmu") || !strcasecmp(codec, "pcma"))
75
                        codec = "g711";
76 ff2d3e1a Lorenzo Miniero
        } else if(!strcasecmp(codec, "text")) {
77
                /* FIXME We only handle text on data channels, so that's the only thing we can save too */
78
                type = JANUS_RECORDER_DATA;
79 b29447b1 Lorenzo Miniero
        } else {
80
                /* We don't recognize the codec: while we might go on anyway, we'd rather fail instead */
81
                JANUS_LOG(LOG_ERR, "Unsupported codec '%s'\n", codec);
82
                return NULL;
83
        }
84
        /* Create the recorder */
85 1f067658 Lorenzo Miniero
        janus_recorder *rc = g_malloc0(sizeof(janus_recorder));
86 9d11ac50 meetecho
        if(rc == NULL) {
87
                JANUS_LOG(LOG_FATAL, "Memory error!\n");
88
                return NULL;
89
        }
90
        rc->dir = NULL;
91
        rc->filename = NULL;
92
        rc->file = NULL;
93 b29447b1 Lorenzo Miniero
        rc->codec = g_strdup(codec);
94 a41aab17 Lorenzo Miniero
        rc->created = janus_get_real_time();
95 9d11ac50 meetecho
        if(dir != NULL) {
96
                /* Check if this directory exists, and create it if needed */
97
                struct stat s;
98
                int err = stat(dir, &s);
99
                if(err == -1) {
100
                        if(ENOENT == errno) {
101
                                /* Directory does not exist, try creating it */
102 5fa9a305 meetecho
                                if(janus_mkdir(dir, 0755) < 0) {
103 9d11ac50 meetecho
                                        JANUS_LOG(LOG_ERR, "mkdir error: %d\n", errno);
104
                                        return NULL;
105
                                }
106
                        } else {
107
                                JANUS_LOG(LOG_ERR, "stat error: %d\n", errno);
108
                                return NULL;
109
                        }
110
                } else {
111
                        if(S_ISDIR(s.st_mode)) {
112
                                /* Directory exists */
113 5fa9a305 meetecho
                                JANUS_LOG(LOG_VERB, "Directory exists: %s\n", dir);
114 9d11ac50 meetecho
                        } else {
115
                                /* File exists but it's not a directory? */
116
                                JANUS_LOG(LOG_ERR, "Not a directory? %s\n", dir);
117
                                return NULL;
118
                        }
119
                }
120
        }
121
        char newname[1024];
122
        memset(newname, 0, 1024);
123
        if(filename == NULL) {
124
                /* Choose a random username */
125 becc0c84 Lorenzo Miniero
                if(!rec_tempname) {
126
                        /* Use .mjr as an extension right away */
127
                        g_snprintf(newname, 1024, "janus-recording-%"SCNu32".mjr", janus_random_uint32());
128
                } else {
129
                        /* Append the temporary extension to .mjr, we'll rename when closing */
130
                        g_snprintf(newname, 1024, "janus-recording-%"SCNu32".mjr.%s", janus_random_uint32(), rec_tempext);
131
                }
132 9d11ac50 meetecho
        } else {
133
                /* Just append the extension */
134 becc0c84 Lorenzo Miniero
                if(!rec_tempname) {
135
                        /* Use .mjr as an extension right away */
136
                        g_snprintf(newname, 1024, "%s.mjr", filename);
137
                } else {
138
                        /* Append the temporary extension to .mjr, we'll rename when closing */
139
                        g_snprintf(newname, 1024, "%s.mjr.%s", filename, rec_tempext);
140
                }
141 9d11ac50 meetecho
        }
142
        /* Try opening the file now */
143
        if(dir == NULL) {
144
                rc->file = fopen(newname, "wb");
145
        } else {
146
                char path[1024];
147
                memset(path, 0, 1024);
148 c94052c2 meetecho
                g_snprintf(path, 1024, "%s/%s", dir, newname);
149 9d11ac50 meetecho
                rc->file = fopen(path, "wb");
150
        }
151
        if(rc->file == NULL) {
152
                JANUS_LOG(LOG_ERR, "fopen error: %d\n", errno);
153
                return NULL;
154
        }
155
        if(dir)
156
                rc->dir = g_strdup(dir);
157
        rc->filename = g_strdup(newname);
158 ff2d3e1a Lorenzo Miniero
        rc->type = type;
159 6089eb4e Lorenzo Miniero
        /* Write the first part of the header */
160 9d11ac50 meetecho
        fwrite(header, sizeof(char), strlen(header), rc->file);
161
        rc->writable = 1;
162 6089eb4e Lorenzo Miniero
        /* We still need to also write the info header first */
163
        rc->header = 0;
164 9d11ac50 meetecho
        janus_mutex_init(&rc->mutex);
165
        return rc;
166
}
167
168 ff2d3e1a Lorenzo Miniero
int janus_recorder_save_frame(janus_recorder *recorder, char *buffer, uint length) {
169 9d11ac50 meetecho
        if(!recorder)
170
                return -1;
171 a3359f76 meetecho
        janus_mutex_lock_nodebug(&recorder->mutex);
172 9d11ac50 meetecho
        if(!buffer || length < 1) {
173 a3359f76 meetecho
                janus_mutex_unlock_nodebug(&recorder->mutex);
174 9d11ac50 meetecho
                return -2;
175
        }
176
        if(!recorder->file) {
177 a3359f76 meetecho
                janus_mutex_unlock_nodebug(&recorder->mutex);
178 9d11ac50 meetecho
                return -3;
179
        }
180
        if(!recorder->writable) {
181 a3359f76 meetecho
                janus_mutex_unlock_nodebug(&recorder->mutex);
182 9d11ac50 meetecho
                return -4;
183
        }
184 6089eb4e Lorenzo Miniero
        if(!recorder->header) {
185
                /* Write info header as a JSON formatted info */
186
                json_t *info = json_object();
187
                /* FIXME Codecs should be configurable in the future */
188 ff2d3e1a Lorenzo Miniero
                const char *type = NULL;
189
                if(recorder->type == JANUS_RECORDER_AUDIO)
190
                        type = "a";
191
                else if(recorder->type == JANUS_RECORDER_VIDEO)
192
                        type = "v";
193
                else if(recorder->type == JANUS_RECORDER_DATA)
194
                        type = "d";
195
                json_object_set_new(info, "t", json_string(type));                                                                /* Audio/Video/Data */
196 b29447b1 Lorenzo Miniero
                json_object_set_new(info, "c", json_string(recorder->codec));                                        /* Media codec */
197 6089eb4e Lorenzo Miniero
                json_object_set_new(info, "s", json_integer(recorder->created));                                /* Created time */
198 a41aab17 Lorenzo Miniero
                json_object_set_new(info, "u", json_integer(janus_get_real_time()));                        /* First frame written time */
199 6089eb4e Lorenzo Miniero
                gchar *info_text = json_dumps(info, JSON_PRESERVE_ORDER);
200
                json_decref(info);
201
                uint16_t info_bytes = htons(strlen(info_text));
202
                fwrite(&info_bytes, sizeof(uint16_t), 1, recorder->file);
203
                fwrite(info_text, sizeof(char), strlen(info_text), recorder->file);
204 df5b546d Lorenzo Miniero
                free(info_text);
205 6089eb4e Lorenzo Miniero
                /* Done */
206
                recorder->header = 1;
207
        }
208 9d11ac50 meetecho
        /* Write frame header */
209 6089eb4e Lorenzo Miniero
        fwrite(frame_header, sizeof(char), strlen(frame_header), recorder->file);
210 ff2d3e1a Lorenzo Miniero
        uint16_t header_bytes = htons(recorder->type == JANUS_RECORDER_DATA ? (length+sizeof(gint64)) : length);
211 9d11ac50 meetecho
        fwrite(&header_bytes, sizeof(uint16_t), 1, recorder->file);
212 ff2d3e1a Lorenzo Miniero
        if(recorder->type == JANUS_RECORDER_DATA) {
213
                /* If it's data, then we need to prepend timing related info, as it's not there by itself */
214
                gint64 now = htonll(janus_get_real_time());
215
                fwrite(&now, sizeof(gint64), 1, recorder->file);
216
        }
217 9d11ac50 meetecho
        /* Save packet on file */
218
        int temp = 0, tot = length;
219
        while(tot > 0) {
220
                temp = fwrite(buffer+length-tot, sizeof(char), tot, recorder->file);
221
                if(temp <= 0) {
222
                        JANUS_LOG(LOG_ERR, "Error saving frame...\n");
223 a3359f76 meetecho
                        janus_mutex_unlock_nodebug(&recorder->mutex);
224 9d11ac50 meetecho
                        return -5;
225
                }
226
                tot -= temp;
227
        }
228
        /* Done */
229 a3359f76 meetecho
        janus_mutex_unlock_nodebug(&recorder->mutex);
230 9d11ac50 meetecho
        return 0;
231
}
232
233
int janus_recorder_close(janus_recorder *recorder) {
234
        if(!recorder || !recorder->writable)
235
                return -1;
236 a3359f76 meetecho
        janus_mutex_lock_nodebug(&recorder->mutex);
237 9d11ac50 meetecho
        recorder->writable = 0;
238
        if(recorder->file) {
239
                fseek(recorder->file, 0L, SEEK_END);
240
                size_t fsize = ftell(recorder->file);
241
                fseek(recorder->file, 0L, SEEK_SET);
242
                JANUS_LOG(LOG_INFO, "File is %zu bytes: %s\n", fsize, recorder->filename);
243
        }
244 becc0c84 Lorenzo Miniero
        if(rec_tempname) {
245
                /* We need to rename the file, to remove the temporary extension */
246
                char newname[1024];
247
                memset(newname, 0, 1024);
248 2a6903c3 Neil Kinnish
                g_snprintf(newname, strlen(recorder->filename)-strlen(rec_tempext), "%s", recorder->filename);
249
                char oldpath[1024];
250
                memset(oldpath, 0, 1024);
251
                char newpath[1024];
252
                memset(newpath, 0, 1024);
253 70043a5e Neil Kinnish
                if(recorder->dir) {
254 2a6903c3 Neil Kinnish
                        g_snprintf(newpath, 1024, "%s/%s", recorder->dir, newname);
255
                        g_snprintf(oldpath, 1024, "%s/%s", recorder->dir, recorder->filename);
256
                } else {
257
                        g_snprintf(newpath, 1024, "%s", newname);
258
                        g_snprintf(oldpath, 1024, "%s", recorder->filename);
259 70043a5e Neil Kinnish
                }
260 1fa9d739 Lorenzo Miniero
                if(rename(oldpath, newpath) != 0) {
261 becc0c84 Lorenzo Miniero
                        JANUS_LOG(LOG_ERR, "Error renaming %s to %s...\n", recorder->filename, newname);
262
                } else {
263
                        JANUS_LOG(LOG_INFO, "Recording renamed: %s\n", newname);
264
                        g_free(recorder->filename);
265
                        recorder->filename = g_strdup(newname);
266
                }
267
        }
268 a3359f76 meetecho
        janus_mutex_unlock_nodebug(&recorder->mutex);
269 9d11ac50 meetecho
        return 0;
270
}
271
272
int janus_recorder_free(janus_recorder *recorder) {
273
        if(!recorder)
274
                return -1;
275
        janus_recorder_close(recorder);
276 a3359f76 meetecho
        janus_mutex_lock_nodebug(&recorder->mutex);
277 b29447b1 Lorenzo Miniero
        g_free(recorder->dir);
278 9d11ac50 meetecho
        recorder->dir = NULL;
279 b29447b1 Lorenzo Miniero
        g_free(recorder->filename);
280 9d11ac50 meetecho
        recorder->filename = NULL;
281 b29447b1 Lorenzo Miniero
        fclose(recorder->file);
282 9d11ac50 meetecho
        recorder->file = NULL;
283 b29447b1 Lorenzo Miniero
        g_free(recorder->codec);
284
        recorder->codec = NULL;
285 a3359f76 meetecho
        janus_mutex_unlock_nodebug(&recorder->mutex);
286 b6b6ac65 meetecho
        g_free(recorder);
287 9d11ac50 meetecho
        return 0;
288
}