Browse Source

TT#111357 add support for configurable output file name patterns

Change-Id: I8662a2ee93700329ee2641c536be9ea8306f6b89
pull/1218/head
Richard Fuchs 5 years ago
parent
commit
c8d6a45777
7 changed files with 158 additions and 17 deletions
  1. +11
    -7
      recording-daemon/main.c
  2. +1
    -0
      recording-daemon/main.h
  3. +1
    -3
      recording-daemon/metafile.c
  4. +89
    -3
      recording-daemon/output.c
  5. +1
    -1
      recording-daemon/output.h
  6. +3
    -3
      recording-daemon/packet.c
  7. +52
    -0
      recording-daemon/rtpengine-recording.pod

+ 11
- 7
recording-daemon/main.c View File

@ -42,6 +42,7 @@ int output_enabled = 1;
mode_t output_chmod; mode_t output_chmod;
uid_t output_chown = -1; uid_t output_chown = -1;
gid_t output_chgrp = -1; gid_t output_chgrp = -1;
char *output_pattern = NULL;
int decoding_enabled; int decoding_enabled;
char *c_mysql_host, char *c_mysql_host,
*c_mysql_user, *c_mysql_user,
@ -83,14 +84,8 @@ static void setup(void) {
socket_init(); socket_init();
if (decoding_enabled) if (decoding_enabled)
codeclib_init(0); codeclib_init(0);
if (output_enabled) {
if (output_enabled)
output_init(output_format); 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); mysql_library_init(0, NULL, NULL);
signals(); signals();
metafile_setup(); 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" }, { "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-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-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" }, { "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" }, { "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" }, { "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) if (num_threads <= 0)
num_threads = num_cpu_cores(8); 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) { static void options_free(void) {
@ -288,6 +291,7 @@ static void options_free(void) {
g_free(c_mysql_db); g_free(c_mysql_db);
g_free(forward_to); g_free(forward_to);
g_free(tls_send_to); g_free(tls_send_to);
g_free(output_pattern);
// free common config options // free common config options
config_load_free(&rtpe_common_config); config_load_free(&rtpe_common_config);


+ 1
- 0
recording-daemon/main.h View File

@ -24,6 +24,7 @@ extern int output_enabled;
extern mode_t output_chmod; extern mode_t output_chmod;
extern uid_t output_chown; extern uid_t output_chown;
extern gid_t output_chgrp; extern gid_t output_chgrp;
extern char *output_pattern;
extern int decoding_enabled; extern int decoding_enabled;
extern char *c_mysql_host, extern char *c_mysql_host,
*c_mysql_user, *c_mysql_user,


+ 1
- 3
recording-daemon/metafile.c View File

@ -73,9 +73,7 @@ static void meta_stream_interface(metafile_t *mf, unsigned long snum, char *cont
if (output_enabled && output_mixed) { if (output_enabled && output_mixed) {
pthread_mutex_lock(&mf->mix_lock); pthread_mutex_lock(&mf->mix_lock);
if (!mf->mix) { 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(); mf->mix = mix_new();
db_do_stream(mf, mf->mix_out, "mixed", NULL, 0); db_do_stream(mf, mf->mix_out, "mixed", NULL, 0);
} }


+ 89
- 3
recording-daemon/output.c View File

@ -6,6 +6,7 @@
#include <glib.h> #include <glib.h>
#include <fcntl.h> #include <fcntl.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/time.h>
#include "log.h" #include "log.h"
#include "db.h" #include "db.h"
#include "main.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)); output_t *ret = g_slice_alloc0(sizeof(*ret));
ret->file_path = g_strdup(path); 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->file_format = output_file_format;
ret->encoder = encoder_new(); 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; return ret;
} }


+ 1
- 1
recording-daemon/output.h View File

@ -10,7 +10,7 @@ extern int mp3_bitrate;
void output_init(const char *format); 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 *); void output_close(output_t *);
int output_config(output_t *output, const format_t *requested_format, format_t *actual_format); int output_config(output_t *output, const format_t *requested_format, format_t *actual_format);


+ 3
- 3
recording-daemon/packet.c View File

@ -177,9 +177,9 @@ out:
dbg("Init for SSRC %s%lx%s of stream #%lu", FMT_M(ret->ssrc), stream->id); dbg("Init for SSRC %s%lx%s of stream #%lu", FMT_M(ret->ssrc), stream->id);
if (mf->recording_on && !ret->output && output_single) { 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); db_do_stream(mf, ret->output, "single", stream, ssrc);
} }
if ((stream->forwarding_on || mf->forwarding_on) && !ret->tls_fwd_stream) { if ((stream->forwarding_on || mf->forwarding_on) && !ret->tls_fwd_stream) {


+ 52
- 0
recording-daemon/rtpengine-recording.pod View File

@ -140,6 +140,58 @@ Path for media files to be written to if file output is enabled. Defaults to
F</var/lib/rtpengine-recording>. The path must not be the same as used for the F</var/lib/rtpengine-recording>. The path must not be the same as used for the
B<spool-dir>. B<spool-dir>.
=item B<--output-pattern=>I<STRING>
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<printf>-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<single> streams this is the SSRC written as hexadecimal;
for B<mix> stream this is the string B<mix>. 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<date(1)>
or L<strftime(3)> (year, month, day, hours, minutes, and seconds,
respectively).
=item B<%>I<INT>
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<abcdefgh> and the output pattern is configured as
B<%2/%3/%c>, then the resulting output file name would be B<ab/cde/abcdefgh>.
=back
=item B<--output-format=>B<wav>|B<mp3>|B<none> =item B<--output-format=>B<wav>|B<mp3>|B<none>
File format to be used for media files that are produced. Defaults to PCM WAV File format to be used for media files that are produced. Defaults to PCM WAV


Loading…
Cancel
Save