diff --git a/docs/rtpengine-recording.md b/docs/rtpengine-recording.md index 71bbb839f..4dd138133 100644 --- a/docs/rtpengine-recording.md +++ b/docs/rtpengine-recording.md @@ -138,12 +138,15 @@ sufficient for a standard installation of rtpengine. Points to the shared object file (__.so__) containing the reference implementation for the EVS codec. See the `README` for more details. -- __\-\-output-storage=file__\|__db__\|__both__ +- __\-\-output-storage=file__\|__db__\|__db-mem__\|__both__ Where to store media files. By default, media files are written directly to the file system (see __output-dir__). They can also be stored as a __BLOB__ in a MySQL database, either instead of, or in addition to, being written to the file - system. + system. Database storage can either be facilitated using temporary files + (__db__) which are then read back and deleted, or without temporary files + (__db-mem__) by spooling the audio data in memory until the recording is + complete. - __\-\-output-dir=__*PATH* diff --git a/recording-daemon/db.c b/recording-daemon/db.c index 1cb898b8f..4f0659417 100644 --- a/recording-daemon/db.c +++ b/recording-daemon/db.c @@ -370,7 +370,7 @@ void db_close_call(metafile_t *mf) { } } -void db_close_stream(output_t *op, FILE *fp) { +void db_close_stream(output_t *op, FILE *fp, GString *gs) { if (check_conn() || op->db_id == 0) { if (fp) fclose(fp); @@ -383,40 +383,48 @@ void db_close_stream(output_t *op, FILE *fp) { MYSQL_BIND b[3]; if ((output_storage & OUTPUT_STORAGE_DB)) { - FILE *f = fp; - if (!f) - f = fopen(op->filename, "rb"); - if (!f) { - ilog(LOG_ERR, "Failed to open file: %s%s%s", FMT_M(op->filename)); - if ((output_storage & OUTPUT_STORAGE_FILE)) - goto file; - return; + if (gs) { + if (fp) + fclose(fp); + stream.len = gs->len; + stream.s = g_string_free(gs, FALSE); } - fseek(f, 0, SEEK_END); - long pos = ftell(f); - if (pos < 0) { - ilog(LOG_ERR, "Failed to get file position: %s", strerror(errno)); - fclose(f); - if ((output_storage & OUTPUT_STORAGE_FILE)) - goto file; - return; - } - stream.len = pos; - fseek(f, 0, SEEK_SET); - stream.s = malloc(stream.len); - if (stream.s) { - size_t count = fread(stream.s, 1, stream.len, f); - if (count != stream.len) { - stream.len = 0; - ilog(LOG_ERR, "Failed to read from stream"); + else { + FILE *f = fp; + if (!f) + f = fopen(op->filename, "rb"); + if (!f) { + ilog(LOG_ERR, "Failed to open file: %s%s%s", FMT_M(op->filename)); + if ((output_storage & OUTPUT_STORAGE_FILE)) + goto file; + return; + } + fseek(f, 0, SEEK_END); + long pos = ftell(f); + if (pos < 0) { + ilog(LOG_ERR, "Failed to get file position: %s", strerror(errno)); fclose(f); if ((output_storage & OUTPUT_STORAGE_FILE)) goto file; - free(stream.s); return; } + stream.len = pos; + fseek(f, 0, SEEK_SET); + stream.s = g_malloc(stream.len); + if (stream.s) { + size_t count = fread(stream.s, 1, stream.len, f); + if (count != stream.len) { + stream.len = 0; + ilog(LOG_ERR, "Failed to read from stream"); + fclose(f); + if ((output_storage & OUTPUT_STORAGE_FILE)) + goto file; + g_free(stream.s); + return; + } + } + fclose(f); } - fclose(f); } else if (fp) fclose(fp); @@ -430,9 +438,8 @@ file:; execute_wrap(&stm_close_stream, b, NULL); - if (stream.s) - free(stream.s); - if (!(output_storage & OUTPUT_STORAGE_FILE)) + g_free(stream.s); + if (op->filename && !(output_storage & OUTPUT_STORAGE_FILE)) if (unlink(op->filename)) ilog(LOG_ERR, "Failed to delete file '%s': %s", op->filename, strerror(errno)); } diff --git a/recording-daemon/db.h b/recording-daemon/db.h index c4d5d1c2b..1fbe8adc5 100644 --- a/recording-daemon/db.h +++ b/recording-daemon/db.h @@ -7,7 +7,7 @@ void db_do_call(metafile_t *); void db_close_call(metafile_t *); void db_do_stream(metafile_t *mf, output_t *op, stream_t *, unsigned long ssrc); -void db_close_stream(output_t *op, FILE *); +void db_close_stream(output_t *op, FILE *, GString *); void db_delete_stream(metafile_t *, output_t *op); void db_config_stream(output_t *op); void db_thread_end(void); diff --git a/recording-daemon/main.c b/recording-daemon/main.c index ed43aaa53..f1e5269b3 100644 --- a/recording-daemon/main.c +++ b/recording-daemon/main.c @@ -285,6 +285,8 @@ static void options(int *argc, char ***argv) { output_storage = OUTPUT_STORAGE_FILE; else if (!strcmp(os_str, "db")) output_storage = OUTPUT_STORAGE_DB; + else if (!strcmp(os_str, "db-mem")) + output_storage = OUTPUT_STORAGE_DB | OUTPUT_STORAGE_DB_MEMORY; else if (!strcmp(os_str, "both")) output_storage = OUTPUT_STORAGE_BOTH; else diff --git a/recording-daemon/main.h b/recording-daemon/main.h index 5b2f2f416..1ddca8485 100644 --- a/recording-daemon/main.h +++ b/recording-daemon/main.h @@ -11,6 +11,7 @@ enum output_storage_enum { OUTPUT_STORAGE_FILE = 0x1, OUTPUT_STORAGE_DB = 0x2, OUTPUT_STORAGE_BOTH = 0x3, + OUTPUT_STORAGE_DB_MEMORY = 0x4, }; enum mix_method { MM_DIRECT = 0, diff --git a/recording-daemon/output.c b/recording-daemon/output.c index fbcb880ca..53a3f9f28 100644 --- a/recording-daemon/output.c +++ b/recording-daemon/output.c @@ -24,7 +24,7 @@ int mp3_bitrate; -static bool output_shutdown(output_t *output, FILE **); +static bool output_shutdown(output_t *output, FILE **, GString **); @@ -269,6 +269,24 @@ int64_t output_avio_seek(void *opaque, int64_t offset, int whence) { return ftell(o->fp); } +int output_avio_mem_write(void *opaque, const uint8_t *buf, int buf_size) { + output_t *o = opaque; + g_string_overwrite_len(o->membuf, o->mempos, (const char *) buf, buf_size); + o->mempos += buf_size; + return buf_size; +} + +int64_t output_avio_mem_seek(void *opaque, int64_t offset, int whence) { + output_t *o = opaque; + if (whence == SEEK_SET) + o->mempos = offset; + else if (whence == SEEK_CUR) + o->mempos += offset; + else if (whence == SEEK_END) + o->mempos = o->membuf->len + offset; + return o->mempos; +} + int output_config(output_t *output, const format_t *requested_format, format_t *actual_format) { const char *err; int av_ret = 0; @@ -284,7 +302,7 @@ int output_config(output_t *output, const format_t *requested_format, format_t * if (G_LIKELY(format_eq(&req_fmt, &output->requested_format))) goto done; - output_shutdown(output, NULL); + output_shutdown(output, NULL, NULL); err = "failed to alloc format context"; output->fmtctx = avformat_alloc_context(); @@ -328,6 +346,10 @@ int output_config(output_t *output, const format_t *requested_format, format_t * #endif char *full_fn = NULL; + + if ((output_storage & OUTPUT_STORAGE_DB_MEMORY)) + goto no_output_file; + char suff[16] = ""; for (int i = 1; i < 20; i++) { if (!output->skip_filename_extension) { @@ -350,9 +372,11 @@ got_fn: output->filename = full_fn; err = "failed to open output file"; - output->fp = fopen(full_fn, (output_storage & OUTPUT_STORAGE_DB) ? "wb+" : "wb"); - if (!output->fp) - goto err; + if (!(output_storage & OUTPUT_STORAGE_DB_MEMORY)) { + output->fp = fopen(full_fn, (output_storage & OUTPUT_STORAGE_DB) ? "wb+" : "wb"); + if (!output->fp) + goto err; + } if (output_buffer > 0) { err = "failed to alloc I/O buffer"; @@ -370,13 +394,20 @@ got_fn: goto err; } +no_output_file: err = "failed to alloc avio buffer"; void *avio_buf = av_malloc(DEFAULT_AVIO_BUFSIZE); if (!avio_buf) goto err; - output->avioctx = avio_alloc_context(avio_buf, DEFAULT_AVIO_BUFSIZE, 1, output, - NULL, output_avio_write, output_avio_seek); + if (!(output_storage & OUTPUT_STORAGE_DB_MEMORY)) + output->avioctx = avio_alloc_context(avio_buf, DEFAULT_AVIO_BUFSIZE, 1, output, + NULL, output_avio_write, output_avio_seek); + else { + output->membuf = g_string_new(""); + output->avioctx = avio_alloc_context(avio_buf, DEFAULT_AVIO_BUFSIZE, 1, output, + NULL, output_avio_mem_write, output_avio_mem_seek); + } err = "failed to alloc AVIOContext"; if (!output->avioctx) { av_freep(&avio_buf); @@ -390,12 +421,12 @@ got_fn: if (av_ret) goto err; - if (output_chmod) + if (output_chmod && output->filename) if (chmod(output->filename, output_chmod)) ilog(LOG_WARN, "Failed to change file mode of '%s%s%s': %s", FMT_M(output->filename), strerror(errno)); - if (output_chown != -1 || output_chgrp != -1) + if ((output_chown != -1 || output_chgrp != -1) && output->filename) if (chown(output->filename, output_chown, output_chgrp)) ilog(LOG_WARN, "Failed to change file owner/group of '%s%s%s': %s", FMT_M(output->filename), strerror(errno)); @@ -405,14 +436,14 @@ got_fn: } db_config_stream(output); - ilog(LOG_INFO, "Opened output media file '%s' for writing", full_fn); + ilog(LOG_INFO, "Opened output media file '%s' for writing", full_fn ?: "(mem stream)"); done: if (actual_format) *actual_format = output->actual_format; return 0; err: - output_shutdown(output, NULL); + output_shutdown(output, NULL, NULL); ilog(LOG_ERR, "Error configuring media output: %s", err); if (av_ret) ilog(LOG_ERR, "Error returned from libav: %s", av_error(av_ret)); @@ -420,13 +451,13 @@ err: } -static bool output_shutdown(output_t *output, FILE **fp) { +static bool output_shutdown(output_t *output, FILE **fp, GString **gs) { if (!output) return false; if (!output->fmtctx) return false; - ilog(LOG_INFO, "Closing output media file '%s'", output->filename); + ilog(LOG_INFO, "Closing output media file '%s'", output->filename ?: "(mem stream)"); bool ret = false; if (output->fmtctx->pb) @@ -443,6 +474,15 @@ static bool output_shutdown(output_t *output, FILE **fp) { fclose(output->fp); output->fp = NULL; } + else if (output->membuf) { + if (output->membuf->len) { + if (gs) { + *gs = output->membuf; + output->membuf = NULL; + } + ret = true; + } + } if (output->avioctx) { if (output->avioctx->buffer) av_freep(&output->avioctx->buffer); @@ -468,16 +508,20 @@ void output_close(metafile_t *mf, output_t *output, tag_t *tag, bool discard) { if (!output) return; if (!discard) { + GString *membuf = NULL; FILE *fp = NULL; - if (output_shutdown(output, &fp)) { - db_close_stream(output, fp); + if (output_shutdown(output, &fp, &membuf)) { + db_close_stream(output, fp, membuf); notify_push_output(output, mf, tag); } - else + else { db_delete_stream(mf, output); + if (membuf) + g_string_free(membuf, TRUE); + } } else { - output_shutdown(output, NULL); + output_shutdown(output, NULL, NULL); if (output->filename && unlink(output->filename)) ilog(LOG_WARN, "Failed to unlink '%s%s%s': %s", FMT_M(output->filename), strerror(errno)); @@ -489,6 +533,8 @@ void output_close(metafile_t *mf, output_t *output, tag_t *tag, bool discard) { g_clear_pointer(&output->file_name, g_free); g_clear_pointer(&output->filename, g_free); g_clear_pointer(&output->iobuf, g_free); + if (output->membuf) + g_string_free(output->membuf, TRUE); g_free(output); } diff --git a/recording-daemon/types.h b/recording-daemon/types.h index c19cd8b67..82c5c0af1 100644 --- a/recording-daemon/types.h +++ b/recording-daemon/types.h @@ -176,6 +176,8 @@ struct output_s { FILE *fp; char *iobuf; + GString *membuf; + size_t mempos; AVIOContext *avioctx; AVFormatContext *fmtctx; AVStream *avst;