diff --git a/recording-daemon/main.c b/recording-daemon/main.c index 958ce0842..d477bb50e 100644 --- a/recording-daemon/main.c +++ b/recording-daemon/main.c @@ -42,6 +42,7 @@ int output_enabled = 1; mode_t output_chmod; uid_t output_chown = -1; gid_t output_chgrp = -1; +char *output_pattern = NULL; int decoding_enabled; char *c_mysql_host, *c_mysql_user, @@ -83,14 +84,8 @@ static void setup(void) { socket_init(); if (decoding_enabled) codeclib_init(0); - if (output_enabled) { + if (output_enabled) output_init(output_format); - if (!g_file_test(output_dir, G_FILE_TEST_IS_DIR)) { - ilog(LOG_INFO, "Creating output dir '%s'", output_dir); - if (mkdir(output_dir, 0700)) - die_errno("Failed to create output dir '%s'", output_dir); - } - } mysql_library_init(0, NULL, NULL); signals(); metafile_setup(); @@ -174,6 +169,7 @@ static void options(int *argc, char ***argv) { { "num-threads", 0, 0, G_OPTION_ARG_INT, &num_threads, "Number of worker threads", "INT" }, { "output-storage", 0, 0, G_OPTION_ARG_STRING, &os_str, "Where to store audio streams", "file|db|both" }, { "output-dir", 0, 0, G_OPTION_ARG_STRING, &output_dir, "Where to write media files to", "PATH" }, + { "output-pattern", 0, 0, G_OPTION_ARG_STRING, &output_pattern,"File name pattern for recordings", "STRING" }, { "output-format", 0, 0, G_OPTION_ARG_STRING, &output_format, "Write audio files of this type", "wav|mp3|none" }, { "resample-to", 0, 0, G_OPTION_ARG_INT, &resample_audio,"Resample all output audio", "INT" }, { "mp3-bitrate", 0, 0, G_OPTION_ARG_INT, &mp3_bitrate, "Bits per second for MP3 encoding", "INT" }, @@ -275,6 +271,13 @@ static void options(int *argc, char ***argv) { if (num_threads <= 0) num_threads = num_cpu_cores(8); + + if (!output_pattern) + output_pattern = g_strdup("%c-%t"); + if (!strstr(output_pattern, "%c")) + die("Invalid output pattern '%s' (no '%%c' format present)", output_pattern); + if (!strstr(output_pattern, "%t")) + die("Invalid output pattern '%s' (no '%%t' format present)", output_pattern); } static void options_free(void) { @@ -288,6 +291,7 @@ static void options_free(void) { g_free(c_mysql_db); g_free(forward_to); g_free(tls_send_to); + g_free(output_pattern); // free common config options config_load_free(&rtpe_common_config); diff --git a/recording-daemon/main.h b/recording-daemon/main.h index 482ef7ed6..35c46ac51 100644 --- a/recording-daemon/main.h +++ b/recording-daemon/main.h @@ -24,6 +24,7 @@ extern int output_enabled; extern mode_t output_chmod; extern uid_t output_chown; extern gid_t output_chgrp; +extern char *output_pattern; extern int decoding_enabled; extern char *c_mysql_host, *c_mysql_user, diff --git a/recording-daemon/metafile.c b/recording-daemon/metafile.c index 121bf8f7d..ffb916a22 100644 --- a/recording-daemon/metafile.c +++ b/recording-daemon/metafile.c @@ -73,9 +73,7 @@ static void meta_stream_interface(metafile_t *mf, unsigned long snum, char *cont if (output_enabled && output_mixed) { pthread_mutex_lock(&mf->mix_lock); if (!mf->mix) { - char buf[256]; - snprintf(buf, sizeof(buf), "%s-mix", mf->parent); - mf->mix_out = output_new(output_dir, buf); + mf->mix_out = output_new(output_dir, mf->parent, "mix"); mf->mix = mix_new(); db_do_stream(mf, mf->mix_out, "mixed", NULL, 0); } diff --git a/recording-daemon/output.c b/recording-daemon/output.c index 02cd24171..e59751ab9 100644 --- a/recording-daemon/output.c +++ b/recording-daemon/output.c @@ -6,6 +6,7 @@ #include #include #include +#include #include "log.h" #include "db.h" #include "main.h" @@ -46,13 +47,98 @@ int output_add(output_t *output, AVFrame *frame) { } -output_t *output_new(const char *path, const char *filename) { +output_t *output_new(const char *path, const char *call, const char *type) { + // construct output file name + time_t now = time(NULL); + struct tm tm; + localtime_r(&now, &tm); + const char *ax = call; + + GString *f = g_string_new(""); + for (const char *p = output_pattern; *p; p++) { + if (*p != '%') { + g_string_append_c(f, *p); + continue; + } + p++; + switch (*p) { + case '\0': + ilog(LOG_ERR, "Invalid output pattern (trailing %%)"); + goto done; + case '%': + g_string_append_c(f, '%'); + break; + case 'c': + g_string_append(f, call); + break; + case 't': + g_string_append(f, type); + break; + case 'Y': + g_string_append_printf(f, "%04i", tm.tm_year + 1900); + break; + case 'm': + g_string_append_printf(f, "%02i", tm.tm_mon + 1); + break; + case 'd': + g_string_append_printf(f, "%02i", tm.tm_mday); + break; + case 'H': + g_string_append_printf(f, "%02i", tm.tm_hour); + break; + case 'M': + g_string_append_printf(f, "%02i", tm.tm_min); + break; + case 'S': + g_string_append_printf(f, "%02i", tm.tm_sec); + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9':; + char *end; + long len = strtol(p, &end, 10); + if (len <= 0 || len == LONG_MAX || end == p) { + ilog(LOG_ERR, "Invalid output pattern (invalid number at '%%%s')", p); + break; + } + while (*ax && len--) + g_string_append_c(f, *ax++); + p = end - 1; // will be advanced +1 in the next loop + break; + default: + ilog(LOG_ERR, "Invalid output pattern (unknown format character '%c')", *p); + break; + } + } + +done:; output_t *ret = g_slice_alloc0(sizeof(*ret)); ret->file_path = g_strdup(path); - ret->file_name = g_strdup(filename); - ret->full_filename = g_strdup_printf("%s/%s", path, filename); + ret->file_name = f->str; // stealing the content + ret->full_filename = g_strdup_printf("%s/%s", path, f->str); ret->file_format = output_file_format; ret->encoder = encoder_new(); + + // create parent directories if needed + char *last_sep = strrchr(ret->full_filename, G_DIR_SEPARATOR); + if (last_sep) { + *last_sep = '\0'; + if (g_mkdir_with_parents(ret->full_filename, 0700)) + ilog(LOG_WARN, "Failed to create (parent) directory for '%s': %s", + ret->full_filename, strerror(errno)); + *last_sep = G_DIR_SEPARATOR; + } + + + g_string_free(f, FALSE); + return ret; } diff --git a/recording-daemon/output.h b/recording-daemon/output.h index c7cd7e61b..a65e9a322 100644 --- a/recording-daemon/output.h +++ b/recording-daemon/output.h @@ -10,7 +10,7 @@ extern int mp3_bitrate; void output_init(const char *format); -output_t *output_new(const char *path, const char *filename); +output_t *output_new(const char *path, const char *call, const char *type); void output_close(output_t *); int output_config(output_t *output, const format_t *requested_format, format_t *actual_format); diff --git a/recording-daemon/packet.c b/recording-daemon/packet.c index 7f7817c43..9ba50901f 100644 --- a/recording-daemon/packet.c +++ b/recording-daemon/packet.c @@ -177,9 +177,9 @@ out: dbg("Init for SSRC %s%lx%s of stream #%lu", FMT_M(ret->ssrc), stream->id); if (mf->recording_on && !ret->output && output_single) { - char buf[256]; - snprintf(buf, sizeof(buf), "%s-%08lx", mf->parent, ssrc); - ret->output = output_new(output_dir, buf); + char buf[16]; + snprintf(buf, sizeof(buf), "%08lx", ssrc); + ret->output = output_new(output_dir, mf->parent, buf); db_do_stream(mf, ret->output, "single", stream, ssrc); } if ((stream->forwarding_on || mf->forwarding_on) && !ret->tls_fwd_stream) { diff --git a/recording-daemon/rtpengine-recording.pod b/recording-daemon/rtpengine-recording.pod index c07ba178a..d6180a50a 100644 --- a/recording-daemon/rtpengine-recording.pod +++ b/recording-daemon/rtpengine-recording.pod @@ -140,6 +140,58 @@ Path for media files to be written to if file output is enabled. Defaults to F. The path must not be the same as used for the B. +=item B<--output-pattern=>I + +File name pattern to be used for recording files. The pattern can reference +sub-directories. Parent directories will be created on demand. The default +setting is B<%c-%t>. + +The pattern must include B-style format sequences. Supported format +sequences are: + +=over + +=item B<%%> + +A literal percent sign. + +=item B<%c> + +The call ID. It is mandatory for the output pattern to include this format +sequence. + +=item B<%t> + +The stream type. For B streams this is the SSRC written as hexadecimal; +for B stream this is the string B. It is mandatory for the output +pattern to include this format sequence. + +=item B<%Y> + +=item B<%m> + +=item B<%d> + +=item B<%H> + +=item B<%M> + +=item B<%S> + +These format sequence reference the current system time (when the output file +was created) and are the same as the format sequences supported by L +or L (year, month, day, hours, minutes, and seconds, +respectively). + +=item B<%>I + +References a prefix from the call ID of the given length. If this format +sequence is present more than once, then the prefixes are cumulative. For +example, if the call ID is B and the output pattern is configured as +B<%2/%3/%c>, then the resulting output file name would be B. + +=back + =item B<--output-format=>B|B|B File format to be used for media files that are produced. Defaults to PCM WAV